tools/rules/slot.rule.ts

tools/rules/slot.rule.ts is a file in Coordination Surface. 113 lines of code and 18 definitions.

import type { Finding, Remediation } from "../core/types/segment.types.ts";

import type { RuleContext, RuleDeclaration, RuleResult } from "../core/types/rule.types.ts";
import { basename, dirname } from "node:path";
import { checkNaming, checkPlacement, parseFilename } from "../core/validators/taxonomy.validator.ts";
import { NAMING_CODES } from "../core/types/taxonomy.types.ts";
import type { TaxonomyData } from "../core/types/taxonomy.types.ts";
import { governedPath } from "../core/resolvers/taxonomy.resolver.ts";

const remediate = function remediate(
    code: string,
    path: string,
    name: string,
    parentFolder: string,
    data: TaxonomyData,
): Remediation {
    if (code === "concernMismatch") {
        const expected = data.folderToTag[parentFolder];
        const { parsed } = parseFilename(name, data);
        if (expected !== undefined && parsed !== null) {
            const variant = parsed.variant === null ? "" : `${parsed.variant}.`;
            return {
                action: "rename",
                decide: "both operands are computed and this still does NOT heal, which is a property of the OPERATION rather than of the derivation: renaming a file changes what every importer of it resolves, so the repair is not confined to the artifact the remediation names and applying it alone breaks the tree. A remediation heals where its effect is bounded by its own operands; this one is deterministic and unbounded, and the missing half is a reference rewrite this package does not perform. Take the rename and the importers together, by hand, in one change",
                deterministic: true,
                from: name,
                target: dirname(path),
                to: `${parsed.subject}.${variant}${expected}.${parsed.ext}`,
            };
        }
    }

    const decideBy: Record<string, string> = {
        subjectEqualsConcern:
            "the subject carries no information when it equals the concern — name what the file serves",
        undeclaredSlot:
            "work the vocabulary ladder: pick an existing word, else reason it by the is-a test, else the filename is wrong",
        unparsable:
            "name it <subject>[.<variant>].<concern>.<ext> — read the file, take the narrowest accurate concern, use a declared subject",
    };

    return {
        action: code === "undeclaredSlot" ? "declare" : "rename",
        decide: decideBy[code] ?? `resolve naming code ${code}`,
        deterministic: false,
        from: name,
        target: path,
        to: null,
    };
};

type SlotRoute = "outside" | "reached" | "unplaceable";

interface SlotOutcome {
    readonly path: string;
    readonly route: SlotRoute;
    readonly finding: Finding | null;
}

const namingFinding = function namingFinding(
    path: string,
    root: string,
    parentFolder: string,
    data: TaxonomyData,
): Finding | null {
    const name = basename(path);
    const verdict = checkNaming(name, parentFolder, data);
    if (verdict.ok) {
        return null;
    }

    const remediation = remediate(verdict.code ?? "", path, name, parentFolder, data);
    return {
        actual: name,
        expected: remediation.to,
        healed: false,
        line: 0,
        locus: verdict.evidence,
        path,
        remediation,
        rule: `slot/${verdict.code ?? ""}`,
        stack: [
            { check: "jurisdiction", resolved: `root=${root}` },
            { check: "concernFolder", resolved: parentFolder },
            { check: "naming", resolved: verdict.code ?? "fail" },
        ],
    };
};

const slotOutcome = function slotOutcome(path: string, data: TaxonomyData): SlotOutcome {
    const governed = governedPath(path, data);
    if (governed === null) {
        return { finding: null, path, route: "outside" };
    }

    const placement = checkPlacement(governed.root, governed.segments, data);
    if (!placement.ok || placement.foreign === true) {
        return { finding: null, path, route: "unplaceable" };
    }

    return { finding: namingFinding(path, governed.root, placement.concernFolder ?? "", data), path, route: "reached" };
};

export const rule: RuleDeclaration = {
    check(context: RuleContext): RuleResult {
        const outcomes = context.paths.map((path) => slotOutcome(path, context.taxonomy));
        const pathsOn = (route: SlotRoute): string[] =>
            outcomes.filter((outcome) => outcome.route === route).map((outcome) => outcome.path);

        return {
            derivations: {
                reached: pathsOn("reached"),
                skippedAsOutsideJurisdiction: pathsOn("outside"),
                skippedAsUnplaceable: pathsOn("unplaceable"),
            },
            findings: outcomes.flatMap((outcome) => (outcome.finding === null ? [] : [outcome.finding])),
            healed: [],
        };
    },
    extensions: [],
    heals: false,
    invariant: "every filename slot draws from its closed array and the concern tag matches the parent folder",
    jurisdiction: "taxonomy",
    kinds: [...NAMING_CODES],

    stage: "structure",
};