tools/core/validators/checklist.validator.ts
tools/core/validators/checklist.validator.ts is a file in Coordination Surface. 123 lines of code and 32 definitions.
import type { Block, Counted, Phase } from "../types/checklist.types.ts";
import { HISTORY_HEADINGS, PAST_MARKERS, STATUS_MARKERS } from "../constants/tense.constants.ts";
import { MILESTONE_MARKER, PHASE_MARKER, TASK_MARKER } from "../constants/checklist.constants.ts";
import { carriesMarker } from "../analyzers/marker.analyzer.ts";
const HISTORY_MARKERS = [...PAST_MARKERS, ...STATUS_MARKERS, ...HISTORY_HEADINGS];
export const isTask = function isTask(line: string): boolean {
return line.trimStart().startsWith(TASK_MARKER);
};
export const missingFields = function missingFields(text: string, fields: readonly string[]): string[] {
return fields.filter((field) => !text.includes(field));
};
interface OpenBlock {
readonly line: number;
readonly parts: readonly string[];
}
const CONTINUATION_INDENT = " ";
const blocksOf = function blocksOf(open: OpenBlock | null): Block[] {
return open === null ? [] : [{ line: open.line, text: open.parts.join(" ") }];
};
const continuesBlock = function continuesBlock(line: string): boolean {
return line.trim().length > 0 && line.startsWith(CONTINUATION_INDENT);
};
interface BlockScan {
readonly done: readonly Block[];
readonly open: OpenBlock | null;
}
const blockStep = function blockStep(scan: BlockScan, line: string, index: number): BlockScan {
if (isTask(line)) {
return { done: [...scan.done, ...blocksOf(scan.open)], open: { line: index + 1, parts: [line.trim()] } };
}
if (scan.open !== null && continuesBlock(line)) {
return { ...scan, open: { ...scan.open, parts: [...scan.open.parts, line.trim()] } };
}
return { done: [...scan.done, ...blocksOf(scan.open)], open: null };
};
export const taskBlocks = function taskBlocks(lines: readonly string[]): Block[] {
let scan: BlockScan = { done: [], open: null };
for (const [index, line] of lines.entries()) {
scan = blockStep(scan, line, index);
}
return [...scan.done, ...blocksOf(scan.open)];
};
export const declaresContract = function declaresContract(
blocks: readonly Block[],
fields: readonly string[],
): boolean {
if (fields.length === 0) {
return false;
}
return blocks.some((block) => missingFields(block.text, fields).length === 0);
};
export const count = function count(lines: readonly string[]): Counted {
let milestones = 0;
let phases = 0;
let tasks = 0;
for (const line of lines) {
if (line.startsWith(MILESTONE_MARKER)) {
milestones += 1;
}
if (line.startsWith(PHASE_MARKER)) {
phases += 1;
}
if (isTask(line)) {
tasks += 1;
}
}
return { milestones, phases, tasks };
};
export const historyIn = function historyIn(line: string): string | null {
const lowered = line.toLowerCase();
return HISTORY_MARKERS.find((marker) => carriesMarker(lowered, marker)) ?? null;
};
const SECTION_HEADING = "## ";
interface PhaseState {
readonly open: Phase | null;
readonly argued: boolean;
}
interface PhaseStep {
readonly emitted: readonly Phase[];
readonly next: PhaseState;
}
const CLOSED: PhaseState = { argued: false, open: null };
const scaffolding = function scaffolding(line: string, labels: readonly string[]): boolean {
const upper = line.toUpperCase();
return labels.some((label) => upper.includes(label));
};
const closedOut = function closedOut(state: PhaseState): Phase[] {
return state.open !== null && state.argued ? [state.open] : [];
};
const argues = function argues(line: string, scaffold: readonly string[]): boolean {
return line.trim().length > 0 && !scaffolding(line, scaffold);
};
const phaseStep = function phaseStep(
state: PhaseState,
line: string,
index: number,
scaffold: readonly string[],
): PhaseStep {
if (line.startsWith(PHASE_MARKER)) {
const open = { line: index + 1, title: line.slice(PHASE_MARKER.length).trim() };
return { emitted: closedOut(state), next: { argued: false, open } };
}
if (state.open === null) {
return { emitted: [], next: state };
}
if (isTask(line)) {
return { emitted: [], next: CLOSED };
}
if (line.startsWith(MILESTONE_MARKER) || line.startsWith(SECTION_HEADING)) {
return { emitted: closedOut(state), next: CLOSED };
}
return { emitted: [], next: argues(line, scaffold) ? { ...state, argued: true } : state };
};
export const emptyPhases = function emptyPhases(lines: readonly string[], scaffold: readonly string[] = []): Phase[] {
const out: Phase[] = [];
let state = CLOSED;
for (const [index, line] of lines.entries()) {
const step = phaseStep(state, line, index, scaffold);
out.push(...step.emitted);
state = step.next;
}
return [...out, ...closedOut(state)];
};