# core/analyzers/document.analyzer.ts

> 138 lines of code and 28 definitions.

Tree: GovLab Docs
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/docs#file-docs-core-analyzers-document-analyzer-ts
Source text: https://banes-lab.com/source/docs/core/analyzers/document.analyzer.ts.txt

Listed in [core/analyzers](https://banes-lab.com/api/source/docs/core/analyzers.md), after [core/analyzers/diagram.analyzer.ts](https://banes-lab.com/source/docs/core/analyzers/diagram.analyzer.ts.md) and before [core/analyzers/drift.analyzer.ts](https://banes-lab.com/source/docs/core/analyzers/drift.analyzer.ts.md).

## Definitions

- `refScanOf` (lexical_declaration, line 75, exported)
- `structuralFindings` (lexical_declaration, line 94)
- `sectionFindings` (lexical_declaration, line 67)
- `agentSchemaFindings` (lexical_declaration, line 79)
- `spineFindings` (lexical_declaration, line 86)
- `locationFindings` (lexical_declaration, line 53)
- `analyzeSync` (lexical_declaration, line 109, exported)
- `locationEntryOf` (lexical_declaration, line 123, exported)
- `docMeta` (lexical_declaration, line 35, exported)
- `docNodeFor` (lexical_declaration, line 136, exported)
- `README_TYPE` (lexical_declaration, line 26)
- `NAME_FIELD` (lexical_declaration, line 27)
- `TYPE_FIELD` (lexical_declaration, line 28)
- `CONCERN_FIELD` (lexical_declaration, line 29)
- `MEMBER_DOT` (lexical_declaration, line 30)
- `POSIX_SEPARATOR` (lexical_declaration, line 31)
- `GENERATED_SEGMENT` (lexical_declaration, line 32)
- `README_SUFFIX` (lexical_declaration, line 33)
- `relDoc` (lexical_declaration, line 36, exported)
- `{ fields }` (lexical_declaration, line 37, exported)
- `docFilename` (lexical_declaration, line 38, exported)
- `schema` (lexical_declaration, line 68)
- `agentDir` (lexical_declaration, line 80)
- `profile` (lexical_declaration, line 87)
- `nameFindingFor` (lexical_declaration, line 142, exported)
- `form` (lexical_declaration, line 143, exported)
- `name` (lexical_declaration, line 144, exported)
- `names` (lexical_declaration, line 148, exported)

## Contained in

- [core/analyzers](https://banes-lab.com/anatomy/docs/folder-docs-core-analyzers.md)

## Uses

- [core/analyzers/diagram.analyzer.ts](https://banes-lab.com/source/docs/core/analyzers/diagram.analyzer.ts.md)
- [core/analyzers/markdown.analyzer.ts](https://banes-lab.com/source/docs/core/analyzers/markdown.analyzer.ts.md)
- [core/analyzers/reference.analyzer.ts](https://banes-lab.com/source/docs/core/analyzers/reference.analyzer.ts.md)
- [core/analyzers/vocabulary.analyzer.ts](https://banes-lab.com/source/docs/core/analyzers/vocabulary.analyzer.ts.md)
- [core/converters/document.converter.ts](https://banes-lab.com/source/docs/core/converters/document.converter.ts.md)
- [core/predicates/document.predicate.ts](https://banes-lab.com/source/docs/core/predicates/document.predicate.ts.md)
- [core/selectors/metadata.selector.ts](https://banes-lab.com/source/docs/core/selectors/metadata.selector.ts.md)
- [core/validators/document.validator.ts](https://banes-lab.com/source/docs/core/validators/document.validator.ts.md)
- [core/validators/metadata.validator.ts](https://banes-lab.com/source/docs/core/validators/metadata.validator.ts.md)
- [core/validators/reference.validator.ts](https://banes-lab.com/source/docs/core/validators/reference.validator.ts.md)
- [core/validators/section.validator.ts](https://banes-lab.com/source/docs/core/validators/section.validator.ts.md)

## Used by

- [core/coordinators/validation.coordinator.ts](https://banes-lab.com/source/docs/core/coordinators/validation.coordinator.ts.md)

## Linked from

- [core/analyzers](https://banes-lab.com/anatomy/docs/folder-docs-core-analyzers.md)
- [core/converters](https://banes-lab.com/anatomy/docs/folder-docs-core-converters.md)
- [core/coordinators](https://banes-lab.com/anatomy/docs/folder-docs-core-coordinators.md)
- [core/predicates](https://banes-lab.com/anatomy/docs/folder-docs-core-predicates.md)
- [core/selectors](https://banes-lab.com/anatomy/docs/folder-docs-core-selectors.md)
- [core/validators](https://banes-lab.com/anatomy/docs/folder-docs-core-validators.md)

## Source

```typescript
import type { Categories, Finding, NameFinding } from "#types/finding.types";
import type { DocMeta, DocNode } from "#types/document.types";
import { GENERATED_MARKER, README_FILE } from "#configuration/constants/document.constants";
import { brokenPaths, frontmatterFileRefs } from "#core/analyzers/reference.analyzer";
import { docBasename, docStem, spineProfileFor, stemParts } from "#core/selectors/metadata.selector";
import { isBoundaryFilename, isHarnessOwned, isHostBoundary } from "#core/predicates/document.predicate";
import { AGENT_FRONTMATTER_SCHEMA } from "#configuration/schemas/agent.schema";
import { DOC_ARCH_FRONTMATTER_SCHEMA } from "#configuration/schemas/document.schema";
import { DOC_TYPE_SCHEMAS } from "#configuration/schemas/readme.schema";
import type { DocEntry } from "#types/location.types";
import { REF_CLAIMS } from "#configuration/constants/verb.constants";
import type { RefScan } from "#types/reference.types";
import type { ValidateCtx } from "#types/environment.types";
import { bannedLanguage } from "#core/analyzers/vocabulary.analyzer";
import { conventionHits } from "#core/analyzers/markdown.analyzer";
import { docLocation } from "#core/validators/location.validator";
import { docName } from "#core/validators/filename.validator";
import { docNodeOf } from "#core/converters/document.converter";
import { frontmatterSchema } from "#core/validators/metadata.validator";
import { mermaidHardening } from "#core/analyzers/diagram.analyzer";
import { offSchema } from "#core/validators/section.validator";
import { parseFrontmatter } from "#core/parsers/metadata.parser";
import { refConstructs } from "#core/validators/reference.validator";
import { validateSpine } from "#core/validators/document.validator";

const README_TYPE = "readme";
const NAME_FIELD = "name";
const TYPE_FIELD = "type";
const CONCERN_FIELD = "concern";
const MEMBER_DOT = ".";
const POSIX_SEPARATOR = "/";
const GENERATED_SEGMENT = `${MEMBER_DOT}${GENERATED_MARKER}${MEMBER_DOT}`;
const README_SUFFIX = `${POSIX_SEPARATOR}${README_FILE}`;

export const docMeta = function docMeta(context: ValidateCtx, doc: string, source: string): DocMeta {
    const relDoc = context.relative(doc);
    const { fields } = parseFrontmatter(source);
    const docFilename = docBasename(relDoc);
    return {
        delegated: context.delegatedRoots.some((prefix) => relDoc.startsWith(prefix)),
        doc,
        docFilename,
        fields,
        fmConcern: fields[CONCERN_FIELD] ?? "",
        fmType: fields[TYPE_FIELD],
        harnessOwned: isHarnessOwned(relDoc, context.harnessRoot),
        hostBoundary: isHostBoundary(docFilename, context.boundaryDocs),
        relDoc,
        source,
    };
};

const locationFindings = function locationFindings(context: ValidateCtx, meta: DocMeta): Finding[] {
    if (meta.harnessOwned || meta.hostBoundary || meta.relDoc.includes(GENERATED_SEGMENT)) {
        return [];
    }
    return docLocation({
        concern: meta.fmConcern,
        form: meta.fmType ?? "",
        kind: "authored",
        options: context.locationOptions,
        registries: context.registries,
        relPath: meta.relDoc,
    });
};

const sectionFindings = function sectionFindings(meta: DocMeta): Finding[] {
    const schema = meta.fmType === undefined ? undefined : DOC_TYPE_SCHEMAS[meta.fmType];
    if (schema === undefined || (meta.fmType === README_TYPE && !meta.relDoc.endsWith(README_SUFFIX))) {
        return [];
    }
    return offSchema(meta.source, schema);
};

export const refScanOf = function refScanOf(context: ValidateCtx, meta: DocMeta): RefScan {
    return refConstructs(meta.source, { claims: REF_CLAIMS, verbs: context.userReg.refVerbs });
};

const agentSchemaFindings = function agentSchemaFindings(context: ValidateCtx, meta: DocMeta): Finding[] {
    const agentDir = context.harnessAgentDir;
    return agentDir !== null && meta.relDoc.startsWith(agentDir)
        ? frontmatterSchema(meta.source, AGENT_FRONTMATTER_SCHEMA)
        : [];
};

const spineFindings = function spineFindings(context: ValidateCtx, meta: DocMeta): Finding[] {
    const profile = spineProfileFor(meta.relDoc, context);
    return validateSpine(meta.source, profile.keys, {
        requireFrontmatter: profile.requireFrontmatter,
        requireTitle: profile.requireTitle,
    });
};

const structuralFindings = function structuralFindings(context: ValidateCtx, meta: DocMeta): Categories {
    if (meta.delegated) {
        return { agentSchema: [], docStatus: [], location: [], sections: [], spine: [] };
    }
    return {
        agentSchema: agentSchemaFindings(context, meta),
        docStatus: meta.relDoc.startsWith(context.rootPrefix)
            ? frontmatterSchema(meta.source, DOC_ARCH_FRONTMATTER_SCHEMA)
            : [],
        location: locationFindings(context, meta),
        sections: sectionFindings(meta),
        spine: spineFindings(context, meta),
    };
};

export const analyzeSync = function analyzeSync(context: ValidateCtx, meta: DocMeta): Categories {
    return {
        ...structuralFindings(context, meta),
        broken: [
            ...brokenPaths(context.paths, meta.doc, meta.source),
            ...frontmatterFileRefs(context.paths, meta.doc, meta.source),
        ],
        conventions: conventionHits(meta.source),
        mermaid: mermaidHardening(meta.source),
        refs: refScanOf(context, meta).defects,
        smell: meta.harnessOwned ? [] : bannedLanguage(meta.source),
    };
};

export const locationEntryOf = function locationEntryOf(context: ValidateCtx, meta: DocMeta): DocEntry | null {
    if (meta.delegated || meta.harnessOwned || meta.hostBoundary || isBoundaryFilename(meta.docFilename)) {
        return null;
    }
    const form = meta.fmType ?? "";
    return {
        concern: meta.fmConcern,
        form,
        ...stemParts(docStem(meta.relDoc), context.registries.forms[form]?.tag),
        source: meta.relDoc,
    };
};

export const docNodeFor = function docNodeFor(context: ValidateCtx, meta: DocMeta): DocNode | null {
    return meta.relDoc.startsWith(context.rootPrefix) && typeof meta.fields[NAME_FIELD] === "string"
        ? docNodeOf(meta.relDoc, meta.fields)
        : null;
};

export const nameFindingFor = function nameFindingFor(context: ValidateCtx, meta: DocMeta): NameFinding | null {
    const form = meta.fmType === undefined ? undefined : context.userReg.forms[meta.fmType];
    const name = meta.fields[NAME_FIELD];
    if (!meta.relDoc.startsWith(context.rootPrefix) || name === undefined || form === undefined) {
        return null;
    }
    const names = docName({ activityVerbs: context.userReg.activityVerbs, concern: meta.fmConcern, form, name });
    return names.length > 0 ? { names, relDoc: meta.relDoc } : null;
};
```
