/*
RFC-0142: pure resolution of per-page llms inclusion depth. Maps the raw `output.llms` declaration from system.md to an effective SemanticLlmsPolicy, applying a conservative default-by-semanticType map.
- Do not read files or content — pure function only.
- Do not format llms output — that lives in llms.ts.
- RFC-0142: Initial implementation.
- RFC-0328: Added legal-page default llms depth "exclude".
*/
import type { SemanticLlmsDepth, SemanticLlmsPolicy } from "./models.ts";
/** Raw per-page llms declaration as authored in system.md (`output.llms`). */
export type RawLlmsPolicy =
SemanticLlmsDepth | { depth?: SemanticLlmsDepth; sections?: { exclude?: string[] } };
const VALID_DEPTHS: ReadonlySet = new Set([
"full",
"summary",
"index-only",
"exclude",
]);
/**
* RFC-0142 / RFC-0328: conservative default depth by semanticType. `openSource`
* is unambiguously low-signal across all apps and is demoted by default. Legal
* pages are first-class statutory pages and are excluded from the public AI
* feed by default.
*/
export const LLMS_DEPTH_BY_SEMANTIC_TYPE: Readonly> = {
openSource: "index-only",
legal: "exclude",
};
const DEFAULT_DEPTH: SemanticLlmsDepth = "full";
function isValidDepth(value: unknown): value is SemanticLlmsDepth {
return typeof value === "string" && VALID_DEPTHS.has(value);
}
/**
* Resolve the effective llms policy for a page.
*
* Precedence: explicit `output.llms` (string or object) > default-by-type > `full`.
* An unrecognized explicit depth falls back to the type default (callers may warn).
*/
export function resolveLlmsPolicy(
raw: RawLlmsPolicy | undefined,
semanticType: string,
): SemanticLlmsPolicy {
const typeDefault = LLMS_DEPTH_BY_SEMANTIC_TYPE[semanticType] ?? DEFAULT_DEPTH;
if (raw === undefined || raw === null) {
return { depth: typeDefault };
}
// String shorthand: `output.llms: index-only`
if (typeof raw === "string") {
return { depth: isValidDepth(raw) ? raw : typeDefault };
}
// Object form: `output.llms: { depth, sections }`
const depth = isValidDepth(raw.depth) ? raw.depth : typeDefault;
const excludeIds = raw.sections?.exclude?.filter((id) => typeof id === "string" && id.length > 0);
return {
depth,
...(excludeIds && excludeIds.length > 0 ? { sections: { exclude: excludeIds } } : {}),
};
}