# tools/rules/template.rule.ts

> 148 lines of code and 25 definitions.

Tree: Coordination tree
Language: typescript
Canonical: https://banes-lab.com/anatomy/coordination#file-coordination-tools-rules-template-rule-ts
Source text: https://banes-lab.com/assets/sources/source.ed9ea714255149b1c0c6ad8162db224ed96f4cf7a95715deb0e4807cef2c2a23.generated.txt

## Definitions

- `build` (lexical_declaration, line 101)
- `basenameOf` (lexical_declaration, line 16)
- `finding` (lexical_declaration, line 26)
- `gateFindings` (lexical_declaration, line 81)
- `entriesOf` (lexical_declaration, line 55)
- `templateFindings` (lexical_declaration, line 110)
- `agentFindings` (lexical_declaration, line 99)
- `agentArtifacts` (lexical_declaration, line 59)
- `skillNames` (lexical_declaration, line 66)
- `slash` (lexical_declaration, line 17)
- `isExecuted` (lexical_declaration, line 21)
- `Artifact` (interface_declaration, line 50)
- `root` (lexical_declaration, line 67)
- `TEMPLATE_GATE_DECIDE` (lexical_declaration, line 75)
- `AGENT_GATE_DECIDE` (lexical_declaration, line 78)
- `AgentScope` (interface_declaration, line 94)
- `name` (lexical_declaration, line 100)
- `imported` (lexical_declaration, line 111)
- `rule` (lexical_declaration, line 127, exported)
- `check` (method_definition, line 128, exported)
- `scoped` (lexical_declaration, line 129, exported)
- `artifacts` (lexical_declaration, line 130, exported)
- `scope` (lexical_declaration, line 131, exported)
- `findings` (lexical_declaration, line 136, exported)
- `agents` (lexical_declaration, line 140, exported)

## Uses

- [tools/core/inspectors/agent.inspector.ts](https://banes-lab.com/source/coordination/tools/core/inspectors/agent.inspector.ts.md)

## Used by

- [tools/core/inspectors/agent.inspector.ts](https://banes-lab.com/source/coordination/tools/core/inspectors/agent.inspector.ts.md)
- [tools/core/inspectors/role.inspector.ts](https://banes-lab.com/source/coordination/tools/core/inspectors/role.inspector.ts.md)

## Source

```typescript
import {
    AGENT_ROOT,
    EXECUTED_TEMPLATES,
    MANDATORY_GATES,
    SKILL_ROOT,
    TEMPLATE_ROOT,
} from "../core/constants/template.constants.ts";
import { type Dirent, existsSync, readFileSync, readdirSync } from "node:fs";
import type { RuleContext, RuleDeclaration, RuleResult } from "../core/types/rule.types.ts";
import { checkIdentity, checkKeys } from "../core/inspectors/agent.inspector.ts";
import { AGENT_INDEX } from "../core/constants/board.constants.ts";
import type { Finding } from "../core/types/segment.types.ts";
import { indexedLetters } from "../core/inspectors/index.inspector.ts";
import { underRoots } from "../core/filters/scope.filter.ts";

const basenameOf = function basenameOf(path: string): string {
    const slash = path.lastIndexOf("/");
    return slash === -1 ? path : path.slice(slash + 1);
};

const isExecuted = function isExecuted(path: string): boolean {
    const name = basenameOf(path);
    return EXECUTED_TEMPLATES.some((marker) => name.startsWith(`${marker}.`));
};

const finding = function finding(
    kind: string,
    path: string,
    locus: string,
    actual: string,
    expected: string,
    decide: string,
): Finding {
    return {
        actual,
        expected,
        healed: false,
        line: 1,
        locus,
        path,
        remediation: { action: "declare", decide, deterministic: false, from: locus, target: path, to: null },
        rule: `template/${kind}`,
        stack: [
            { check: "template", resolved: basenameOf(path) },
            { check: kind, resolved: locus },
        ],
    };
};

interface Artifact {
    readonly path: string;
    readonly source: string;
}

const entriesOf = function entriesOf(root: string): Dirent[] {
    return existsSync(root) ? readdirSync(root, { withFileTypes: true }) : [];
};

const agentArtifacts = function agentArtifacts(repoRoot: string): Artifact[] {
    const root = `${repoRoot}/${AGENT_ROOT}`;
    return entriesOf(root)
        .filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
        .map((entry) => ({ path: `${AGENT_ROOT}${entry.name}`, source: readFileSync(`${root}${entry.name}`, "utf8") }));
};

const skillNames = function skillNames(repoRoot: string): Set<string> {
    const root = `${repoRoot}/${SKILL_ROOT}`;
    return new Set(
        entriesOf(root)
            .filter((entry) => entry.isDirectory() && existsSync(`${root}${entry.name}/SKILL.md`))
            .map((entry) => entry.name),
    );
};

const TEMPLATE_GATE_DECIDE =
    "the four gates never fold — worth, admissibility, evidence and termination run on every execution, and a template that omits one cannot be executed as stated";

const AGENT_GATE_DECIDE =
    "an executed artifact walks its template's loop as stated, and the four gates never fold — an agent omitting one was template-shaped rather than template-executed, which is the difference the rule exists to hold";

const gateFindings = function gateFindings(path: string, source: string, decide: string): Finding[] {
    return MANDATORY_GATES.filter((gate) => !source.includes(gate)).map((gate) =>
        finding(
            "gateMissing",
            path,
            gate,
            `${basenameOf(path)} does not declare the ${gate} gate`,
            MANDATORY_GATES.join(", "),
            decide,
        ),
    );
};

interface AgentScope {
    readonly letters: ReadonlySet<string>;
    readonly skills: ReadonlySet<string>;
}

const agentFindings = function agentFindings(artifact: Artifact, scope: AgentScope): Finding[] {
    const name = basenameOf(artifact.path);
    const build = (kind: string, locus: string, actual: string, expected: string, decide: string): Finding =>
        finding(kind, artifact.path, locus, actual, expected, decide);
    return [
        ...checkKeys(name, artifact.source, build),
        ...checkIdentity(name, artifact.source, scope.letters, scope.skills, build),
        ...gateFindings(artifact.path, artifact.source, AGENT_GATE_DECIDE),
    ];
};

const templateFindings = function templateFindings(path: string, source: string, scoped: readonly string[]): Finding[] {
    const imported = scoped.filter((other) => other !== path && source.includes(basenameOf(other)));
    return [
        ...gateFindings(path, source, TEMPLATE_GATE_DECIDE),
        ...imported.map((other) =>
            finding(
                "templateImported",
                path,
                basenameOf(other),
                `${basenameOf(path)} references ${basenameOf(other)}`,
                "each template inlines its whole structure",
                "the templates deliberately duplicate their spine so each stays independently executable; refactoring them toward a shared import is the one place the duplication rule is overruled",
            ),
        ),
    ];
};

export const rule: RuleDeclaration = {
    check(context: RuleContext): RuleResult {
        const scoped = underRoots(context.paths, [TEMPLATE_ROOT]);
        const artifacts = agentArtifacts(context.repoRoot);
        const scope: AgentScope = {
            letters: indexedLetters(context.read(AGENT_INDEX)).letters,
            skills: skillNames(context.repoRoot),
        };

        const findings = [
            ...scoped.filter(isExecuted).flatMap((path) => templateFindings(path, context.read(path), scoped)),
            ...artifacts.flatMap((artifact) => agentFindings(artifact, scope)),
        ];
        const agents = artifacts.map((artifact) => basenameOf(artifact.path));

        return { derivations: { agents, templates: scoped.map(basenameOf) }, findings, healed: [] };
    },
    extensions: [".md"],
    heals: false,
    invariant: "an executed template and every artifact executed from one declare all mandatory gates",
    jurisdiction: "taxonomy",
    kinds: [
        "gateMissing",
        "letterNotInName",
        "participationUndeclared",
        "participationUnindexed",
        "protocolNotPreloaded",
        "skillDoesNotResolve",
        "templateImported",
        "undeliveredKey",
    ],
    readsTree:
        "an agent artifact is the executed half of this rule's subject and it lives in a tree the taxonomy " +
        "declares FOREIGN — its filenames are identifiers a runtime resolves, so the naming gates must not " +
        "reach it and it is absent from every readable path set; the rule reads that tree directly because " +
        "its subject is declared out of scope by design rather than because the rule reaches past its own",

    stage: "content",
};
```
