# tools/core/comparators/snapshot.comparator.ts

> 96 lines of code and 23 definitions.

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

Listed in [tools/core/comparators](https://banes-lab.com/api/source/coordination/tools/core/comparators.md), after [tools/core/comparators/gate.comparator.ts](https://banes-lab.com/source/coordination/tools/core/comparators/gate.comparator.ts.md).

## Definitions

- `judgement` (lexical_declaration, line 8)
- `judgePresent` (lexical_declaration, line 31)
- `judgeAbsent` (lexical_declaration, line 12)
- `compared` (lexical_declaration, line 51)
- `carried` (lexical_declaration, line 19)
- `stranded` (lexical_declaration, line 25)
- `held` (lexical_declaration, line 36)
- `missing` (lexical_declaration, line 37)
- `priorHeld` (lexical_declaration, line 39)
- `difference` (lexical_declaration, line 40)
- `now` (lexical_declaration, line 53)
- `verdictsOf` (lexical_declaration, line 60)
- `baselineOf` (lexical_declaration, line 72)
- `comparison` (lexical_declaration, line 86, exported)
- `extents` (lexical_declaration, line 87, exported)
- `{ current }` (lexical_declaration, line 88, exported)
- `retained` (lexical_declaration, line 89, exported)
- `comparable` (lexical_declaration, line 90, exported)
- `judged` (lexical_declaration, line 91, exported)
- `unseen` (lexical_declaration, line 93, exported)
- `verdicts` (lexical_declaration, line 94, exported)
- `surfaces` (lexical_declaration, line 95, exported)
- `fresh` (lexical_declaration, line 96, exported)

## Uses

- [tools/core/factories/snapshot.factory.ts](https://banes-lab.com/source/coordination/tools/core/factories/snapshot.factory.ts.md)

## Source

```typescript
import { ANCHOR_KINDS, markedElsewhere, measured, sameKinds } from "../analyzers/snapshot.analyzer.ts";
import type { Comparison, Extent, Judgement, Measured, Retained, Verdict } from "../types/snapshot.types.ts";
import { frozenFinding, shortenedFinding } from "../factories/snapshot.factory.ts";
import type { Finding } from "../types/segment.types.ts";
import type { StepOptions } from "../types/rule.types.ts";
import { retainedFrom } from "../readers/snapshot.reader.ts";

const judgement = function judgement(path: string, verdict: Verdict, finding: Finding | null = null): Judgement {
    return { finding, memberless: false, path, renamed: null, verdict };
};

const judgeAbsent = function judgeAbsent(
    path: string,
    prior: Extent,
    retained: Retained,
    extents: Measured,
): Judgement {
    if (prior.anchors.length === 0) {
        const carried = markedElsewhere(prior.mark, retained.surfaces, extents.current);
        return carried === null
            ? { ...judgement(path, "not-comparable"), memberless: true }
            : { ...judgement(path, "relocated"), renamed: `${path} → ${carried}` };
    }

    const stranded = prior.anchors.filter((anchor) => !extents.frozenAnchors.has(anchor));
    return stranded.length === 0
        ? judgement(path, "relocated")
        : judgement(path, "shortened", shortenedFinding(path, stranded, prior.lifetime));
};

const judgePresent = function judgePresent(path: string, prior: Extent, now: Extent, frozen: boolean): Judgement {
    if (now.lifetime !== prior.lifetime) {
        return judgement(path, "not-comparable");
    }

    const held = new Set(now.anchors);
    const missing = prior.anchors.filter((anchor) => !held.has(anchor));
    if (frozen) {
        const priorHeld = new Set(prior.anchors);
        const difference = [...missing, ...now.anchors.filter((anchor) => !priorHeld.has(anchor))];
        return difference.length === 0
            ? judgement(path, "unchanged")
            : judgement(path, "shortened", frozenFinding(path, difference, prior.lifetime));
    }

    return missing.length === 0
        ? judgement(path, "unchanged")
        : judgement(path, "shortened", shortenedFinding(path, missing, prior.lifetime));
};

const compared = function compared(retained: Retained, extents: Measured): Judgement[] {
    return Object.entries(retained.surfaces).map(([path, prior]) => {
        const now = extents.current.get(path);
        return now === undefined
            ? judgeAbsent(path, prior, retained, extents)
            : judgePresent(path, prior, now, extents.frozen.has(path));
    });
};

const verdictsOf = function verdictsOf(
    judged: readonly Judgement[],
    current: ReadonlyMap<string, Extent>,
    unseen: Verdict,
): Record<string, Verdict> {
    const verdicts: Record<string, Verdict> = Object.fromEntries(judged.map((entry) => [entry.path, entry.verdict]));
    for (const path of current.keys()) {
        verdicts[path] ??= unseen;
    }
    return verdicts;
};

const baselineOf = function baselineOf(
    current: ReadonlyMap<string, Extent>,
    retained: Retained | null,
    verdicts: Readonly<Record<string, Verdict>>,
): Record<string, Extent> {
    const surfaces: Record<string, Extent> = Object.fromEntries(current);
    for (const [path, prior] of Object.entries(retained?.surfaces ?? {})) {
        if (verdicts[path] === "shortened") {
            surfaces[path] = prior;
        }
    }
    return surfaces;
};

export const comparison = function comparison(options: StepOptions, paths: readonly string[]): Comparison {
    const extents = measured(options.repoRoot, paths);
    const { current } = extents;
    const retained = retainedFrom(options.repoRoot);
    const comparable = retained !== null && retained.range === options.scope && sameKinds(retained.anchorKinds);
    const judged = comparable ? compared(retained, extents) : [];

    const unseen: Verdict = comparable || retained === null ? "first-seen" : "not-comparable";
    const verdicts = verdictsOf(judged, current, unseen);
    const surfaces = baselineOf(current, retained, verdicts);
    const fresh = options.authoritative || retained === null;
    return {
        carried: fresh ? { anchorKinds: ANCHOR_KINDS, range: options.scope, surfaces } : retained,
        current,
        findings: judged.flatMap((entry) => (entry.finding === null ? [] : [entry.finding])),
        memberless: judged.filter((entry) => entry.memberless).map((entry) => entry.path),
        renamed: judged.flatMap((entry) => (entry.renamed === null ? [] : [entry.renamed])),
        retained,
        surfaces,
        verdicts,
    };
};
```
