tools/core/runners/mark.runner.ts

tools/core/runners/mark.runner.ts is a file in Coordination Surface. 163 lines of code and 42 definitions.

import { AT_FIELD, READ_FIELD, TO_FIELD } from "../formatters/board.formatter.ts";
import { AWAITING_MARKER, BLOCKING_SUFFIX, RECORD_ABSENT, UNREAD_MARKER } from "../constants/blocking.constants.ts";
import { alreadyMarked, itemNotFound, marked, notAddressed } from "../strings/mark.strings.ts";
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { letters, rosterLine } from "./converge.runner.ts";
import {
    rosterContended,
    rosterLinesMissing,
    rosterNotVenue,
    rosterRegression,
    rosterResolved,
    rosterUnchanged,
    rosterVenueElsewhere,
} from "../strings/venue.strings.ts";
import { surfacePath, surfacePrefix } from "../../../config/surface.config.ts";
import type { RaiseOutcome } from "../types/venue.types.ts";
import { activeSeats } from "../analyzers/board.analyzer.ts";
import { itemSpans } from "../resolvers/sweep.resolver.ts";
import { resolve } from "node:path";
import { surfaceText } from "../readers/venue.reader.ts";
import { venueRefusal } from "../factories/venue.factory.ts";

interface MarkRequest {
    readonly target: string;
    readonly absolute: string;
    readonly item: string;
    readonly agent: string;
}

interface MarkOutcome {
    readonly code: number;
    readonly message: string;
}

export const markedReaders = function markedReaders(marker: string): string[] {
    const at = marker.indexOf(READ_FIELD);
    if (at === -1) {
        return [];
    }

    const rest = marker.slice(at + READ_FIELD.length);
    let value = "";
    for (const character of rest) {
        if (character === " ") {
            break;
        }
        value += character;
    }

    return value.length === 0 ? [] : value.split(",");
};

const withReader = function withReader(marker: string, agent: string): string {
    const readers = markedReaders(marker);
    if (readers.includes(agent)) {
        return marker;
    }

    const joined = [...readers, agent].join(",");
    const at = marker.indexOf(READ_FIELD);

    if (at === -1) {
        return `${marker} ${READ_FIELD}${joined}`;
    }

    const tail = marker.slice(at + READ_FIELD.length);
    let width = 0;
    while (width < tail.length && tail.charAt(width) !== " ") {
        width += 1;
    }

    return `${marker.slice(0, at)}${READ_FIELD}${joined}${tail.slice(width)}`;
};

export const runMark = function runMark(request: MarkRequest): MarkOutcome {
    const source = readFileSync(request.absolute, "utf8");
    const span = itemSpans(source).find((entry) => entry.key === request.item);

    if (span === undefined) {
        return { code: 2, message: itemNotFound(request.item, request.target) };
    }

    if (span.to.length > 0 && !span.to.includes(request.agent)) {
        return { code: 2, message: notAddressed(request.item, request.agent) };
    }

    const lines = source.split("\n");
    const marker = lines[span.from] ?? "";

    if (markedReaders(marker).includes(request.agent)) {
        return { code: 0, message: alreadyMarked(request.agent, request.item) };
    }

    lines[span.from] = withReader(marker, request.agent);
    writeFileSync(request.absolute, lines.join("\n"), "utf8");

    return { code: 0, message: marked(request.agent, request.item) };
};

export const healRequested = function healRequested(argv: readonly string[], flag: string): boolean {
    return !argv.includes(flag);
};

export const authorOf = function authorOf(item: string): string {
    const cut = item.indexOf("-");
    return cut <= 0 ? item : item.slice(0, cut);
};

export const unmarkedReaders = function unmarkedReaders(
    marker: string,
    addressed: readonly string[],
    active: readonly string[],
    author = "",
): string[] {
    const readers = markedReaders(marker);
    const named = addressed.length === 0 ? active : addressed;

    return named.filter((letter) => letter !== author && active.includes(letter) && !readers.includes(letter));
};

export const carriesLedger = function carriesLedger(marker: string): boolean {
    return marker.includes(READ_FIELD) || marker.includes(TO_FIELD) || marker.includes(AT_FIELD);
};

export const rosterFor = function rosterFor(
    repoRoot: string,
    markedLetters: readonly string[],
): { unread: string[]; read: string[] } {
    const board = surfaceText(repoRoot, surfacePath("board"));
    const index = surfaceText(repoRoot, surfacePath("agent_index"));
    const seats = activeSeats(board, index);

    const read = seats.filter((letter) => markedLetters.includes(letter));
    return { read, unread: seats.filter((letter) => !markedLetters.includes(letter)) };
};

const rosterRewrite = function rosterRewrite(
    repoRoot: string,
    name: string,
    before: string,
): RaiseOutcome | { composed: string; roster: { unread: string[]; read: string[] } } {
    const lines = before.split("\n");
    const unreadAt = lines.findIndex((line) => line.startsWith(UNREAD_MARKER));
    const readAt = lines.findIndex((line) => line.startsWith(AWAITING_MARKER));
    if (unreadAt === -1 || readAt === -1) {
        return venueRefusal(rosterLinesMissing(name));
    }

    const roster = rosterFor(repoRoot, letters(lines[readAt] ?? "", AWAITING_MARKER));
    const seated = new Set([...roster.unread, ...roster.read]);
    const standing = letters(lines[unreadAt] ?? "", UNREAD_MARKER).filter((letter) => seated.has(letter));
    const regressing = roster.unread.filter((letter) => !standing.includes(letter));
    if (standing.length > 0 && regressing.length > 0) {
        return venueRefusal(rosterRegression(name, regressing));
    }

    const written = [...lines];
    written[unreadAt] = rosterLine(UNREAD_MARKER, roster.unread);
    written[readAt] = rosterLine(AWAITING_MARKER, roster.read);
    return { composed: written.join("\n"), roster };
};

export const runRoster = function runRoster(options: {
    readonly repoRoot: string;
    readonly name: string;
}): RaiseOutcome {
    if (!options.name.endsWith(BLOCKING_SUFFIX)) {
        return venueRefusal(rosterNotVenue(options.name));
    }

    const absolute = resolve(options.repoRoot, surfacePrefix(), options.name);
    if (!existsSync(absolute)) {
        return venueRefusal(rosterVenueElsewhere(options.name));
    }

    const before = readFileSync(absolute, "utf8");
    const rewrite = rosterRewrite(options.repoRoot, options.name, before);
    if (!("composed" in rewrite)) {
        return rewrite;
    }

    const { composed, roster } = rewrite;
    if (composed === before) {
        return { code: 0, message: rosterUnchanged(options.name), raised: options.name };
    }

    const witness = readFileSync(absolute, "utf8");
    if (witness !== before) {
        return venueRefusal(rosterContended(options.name));
    }

    writeFileSync(absolute, composed, "utf8");

    const unread = roster.unread.join(", ") || RECORD_ABSENT;
    const read = roster.read.join(", ") || RECORD_ABSENT;
    return { code: 0, message: rosterResolved(options.name, unread, read), raised: options.name };
};