# tools/core/analyzers/converge.analyzer.ts

> 119 lines of code and 43 definitions.

Tree: Coordination tree
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/coordination#file-coordination-tools-core-analyzers-converge-analyzer-ts
Source text: https://banes-lab.com/source/coordination/tools/core/analyzers/converge.analyzer.ts.txt

Listed in [tools/core/analyzers](https://banes-lab.com/api/source/coordination/tools/core/analyzers.md), after [tools/core/analyzers/checklist.analyzer.ts](https://banes-lab.com/source/coordination/tools/core/analyzers/checklist.analyzer.ts.md) and before [tools/core/analyzers/fence.analyzer.ts](https://banes-lab.com/source/coordination/tools/core/analyzers/fence.analyzer.ts.md).

## Definitions

- `clauseLines` (lexical_declaration, line 148, exported)
- `opensSection` (lexical_declaration, line 103)
- `isSectionBoundary` (lexical_declaration, line 80)
- `isStatedClause` (lexical_declaration, line 99)
- `clauseSection` (lexical_declaration, line 119)
- `isHeadingCharacter` (lexical_declaration, line 34)
- `isDigit` (lexical_declaration, line 30)
- `declaredHeading` (lexical_declaration, line 38, exported)
- `receiverOf` (lexical_declaration, line 61)
- `unemphasised` (lexical_declaration, line 72)
- `deferredClauses` (lexical_declaration, line 139, exported)
- `insideFlags` (lexical_declaration, line 107)
- `headingIsWhole` (lexical_declaration, line 42, exported)
- `clauseOf` (lexical_declaration, line 94)
- `SIGN_OFF_ABSENT` (lexical_declaration, line 4, exported)
- `PLACEHOLDER` (lexical_declaration, line 6, exported)
- `LIST_ITEM` (lexical_declaration, line 8)
- `EMPHASIS` (lexical_declaration, line 10)
- `SUCCESSOR_FIELD` (lexical_declaration, line 12)
- `CLAUSE_ARROW` (lexical_declaration, line 14)
- `BANNER` (lexical_declaration, line 16)
- `DEFERRED_HEADING` (lexical_declaration, line 18)
- `DEFERRED_SECTION` (lexical_declaration, line 20, exported)
- `DEFERRAL_ARROW` (lexical_declaration, line 22, exported)
- `ClauseSection` (interface_declaration, line 24)
- `trimmed` (lexical_declaration, line 43, exported)
- `declaredSuccessor` (lexical_declaration, line 52, exported)
- `fenced` (lexical_declaration, line 53, exported)
- `line` (lexical_declaration, line 54, exported)
- `value` (lexical_declaration, line 57, exported)
- `rest` (lexical_declaration, line 67)
- `comma` (lexical_declaration, line 68)
- `held` (lexical_declaration, line 73)
- `sectionBound` (lexical_declaration, line 84, exported)
- `boundaries` (lexical_declaration, line 86, exported)
- `from` (lexical_declaration, line 87, exported)
- `arrow` (lexical_declaration, line 95)
- `out` (lexical_declaration, line 108)
- `lines` (lexical_declaration, line 120, exported)
- `inside` (lexical_declaration, line 121)
- `items` (lexical_declaration, line 122)
- `clause` (lexical_declaration, line 133)
- `section` (lexical_declaration, line 140, exported)

## Used by

- [tools/core/resolvers/venue.resolver.ts](https://banes-lab.com/source/coordination/tools/core/resolvers/venue.resolver.ts.md)
- [tools/core/runners/blocking.runner.ts](https://banes-lab.com/source/coordination/tools/core/runners/blocking.runner.ts.md)
- [tools/core/validators/converge.validator.ts](https://banes-lab.com/source/coordination/tools/core/validators/converge.validator.ts.md)

## Source

```typescript
import type { ClauseLine, DeferredSection } from "../types/converge.types.ts";
import { fencedFlags } from "../predicates/fence.predicate.ts";

export const SIGN_OFF_ABSENT = "—";

export const PLACEHOLDER = "<";

const LIST_ITEM = "- ";

const EMPHASIS = ["*", "`", "_"];

const SUCCESSOR_FIELD = "SUCCESSOR:";

const CLAUSE_ARROW = "→";

const BANNER = "═";

const DEFERRED_HEADING = "DEFERRED";

export const DEFERRED_SECTION = DEFERRED_HEADING;

export const DEFERRAL_ARROW = CLAUSE_ARROW;

interface ClauseSection {
    readonly declared: boolean;
    readonly answered: boolean;
    readonly lines: readonly ClauseLine[];
}

const isDigit = function isDigit(character: string): boolean {
    return character >= "0" && character <= "9";
};

const isHeadingCharacter = function isHeadingCharacter(character: string): boolean {
    return (character >= "a" && character <= "z") || isDigit(character) || character === "-";
};

export const declaredHeading = function declaredHeading(field: string): string {
    return field.trim();
};

export const headingIsWhole = function headingIsWhole(field: string): boolean {
    const trimmed = field.trim();
    for (const character of trimmed) {
        if (!isHeadingCharacter(character)) {
            return false;
        }
    }
    return trimmed.length > 0;
};

export const declaredSuccessor = function declaredSuccessor(venue: string): string {
    const fenced = fencedFlags(venue);
    const line = venue
        .split("\n")
        .find((text, index) => fenced[index] !== true && text.trim().startsWith(SUCCESSOR_FIELD));
    const value = line === undefined ? "" : line.trim().slice(SUCCESSOR_FIELD.length).trim();
    return value.startsWith(PLACEHOLDER) ? "" : value;
};

const receiverOf = function receiverOf(line: string): string {
    const arrow = line.indexOf(CLAUSE_ARROW);
    if (arrow === -1) {
        return "";
    }

    const rest = line.slice(arrow + CLAUSE_ARROW.length).trim();
    const comma = rest.indexOf(",");
    return (comma === -1 ? rest : rest.slice(0, comma)).trim();
};

const unemphasised = function unemphasised(text: string): string {
    let held = text;
    for (const mark of EMPHASIS) {
        held = held.replaceAll(mark, "");
    }
    return held.trim();
};

const isSectionBoundary = function isSectionBoundary(trimmed: string): boolean {
    return trimmed.startsWith("#") || trimmed.startsWith(BANNER);
};

export const sectionBound = function sectionBound(venue: string, heading: string): { from: number; to: number } | null {
    const lines = venue.split("\n");
    const boundaries = [...lines.keys()].filter((index) => isSectionBoundary((lines[index] ?? "").trim()));
    const from = boundaries.find((index) => (lines[index] ?? "").includes(heading));
    if (from === undefined) {
        return null;
    }
    return { from, to: boundaries.find((index) => index > from) ?? lines.length };
};

const clauseOf = function clauseOf(trimmed: string): string {
    const arrow = trimmed.indexOf(CLAUSE_ARROW);
    return unemphasised(arrow === -1 ? trimmed.slice(LIST_ITEM.length) : trimmed.slice(LIST_ITEM.length, arrow));
};

const isStatedClause = function isStatedClause(clause: string): boolean {
    return clause.length > 0 && !clause.startsWith(PLACEHOLDER) && clause !== SIGN_OFF_ABSENT;
};

const opensSection = function opensSection(trimmed: string, heading: string): boolean {
    return isSectionBoundary(trimmed) && trimmed.includes(heading);
};

const insideFlags = function insideFlags(lines: readonly string[], heading: string): boolean[] {
    const out: boolean[] = [];
    let inside = false;

    for (const trimmed of lines) {
        out.push(inside);
        inside = opensSection(trimmed, heading) || (inside && !isSectionBoundary(trimmed));
    }

    return out;
};

const clauseSection = function clauseSection(venue: string, heading: string): ClauseSection {
    const lines = venue.split("\n").map((line) => line.trim());
    const inside = insideFlags(lines, heading);
    const items = lines.filter(
        (trimmed, index) => inside[index] === true && !isSectionBoundary(trimmed) && trimmed.startsWith(LIST_ITEM),
    );

    return {
        answered: items.some((trimmed) => {
            const clause = clauseOf(trimmed);
            return isStatedClause(clause) || clause === SIGN_OFF_ABSENT;
        }),
        declared: lines.some((trimmed) => opensSection(trimmed, heading)),
        lines: items.flatMap((trimmed) => {
            const clause = clauseOf(trimmed);
            return isStatedClause(clause) ? [{ clause, receiver: receiverOf(trimmed) }] : [];
        }),
    };
};

export const deferredClauses = function deferredClauses(venue: string): DeferredSection {
    const section = clauseSection(venue, DEFERRED_SECTION);
    return {
        answered: section.answered,
        clauses: section.lines.map((entry) => entry.clause),
        declared: section.declared,
    };
};

export const clauseLines = function clauseLines(venue: string, heading: string): readonly ClauseLine[] {
    return clauseSection(venue, heading).lines;
};
```
