check

tcmts.cli.main.check · #jeru34xv · term · runs on the server

uses node:fs

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import {
  changedCode, fileRows, followImports, keptAs, pathsIn, printRows, printScratchLint, withTheirCycles,
} from "#codebase/tcmts/cli/main";
import { conflictAt, conflictedHint, conflictedUses, conflictMarkers } from "#codebase/tcmts/cli/merge";
import { bold, cyan, dim, red } from "#codebase/tcmts/cli/style";
import { evaluateCached, isTestPath, printOutcomes, runCached, summary } from "#codebase/tcmts/cli/testing";
import { scratchShown, shellWord } from "#codebase/tcmts/cli/ui";
import fs from "node:fs";
import path_1 from "node:path";

export function check(
  cb: Codebase,
  file: string,
  into?: string,
  fresh = false,
  imports: "check" | "say" = "check",
): boolean {
  const marker = conflictMarkers(fs.readFileSync(file, "utf8"));
  if (marker) {
    console.log(
      red(
        `${path_1.basename(file)}:${marker} still has conflict markers: keep what each side should, take the markers out, then check it again`,
      ),
    );
    return false;
  }
  const format = keptAs(cb, file, into);
  if (format !== undefined) {
    const rows = fileRows(cb, file, into, format);
    if (!rows) return false;
    console.log(bold(path_1.basename(file)));
    printRows(rows, (d) => cb.store.shownSignature(d));
    const pending = rows.filter((r) => r.status !== "identical");
    if (pending.length)
      console.log(
        dim(
          `\n  run \`tcm ${pending.some((r) => r.status === "update" || r.status === "conflict") ? "update" : "add"} ${shellWord(scratchShown(file, cb.sessionDir))}${into !== undefined ? ` --into ${into}` : ""}\` to save it`,
        ),
      );
    return true;
  }
  // checked as it's saved: names it uses imported (`say`: only said, as `tcm watch` does)
  followImports(cb, file, into, imports);
  const whole = withTheirCycles(cb, file, into);
  if (!whole) {
    // (a conflicted name it uses is why, likely: say so)
    const text = fs.readFileSync(file, "utf8");
    const uses = conflictedUses(cb, file, text);
    if (uses.length)
      console.log(
        `\n${path_1.basename(file)} ${conflictedHint(
          uses,
          pathsIn(file, text, into).filter((p) => conflictAt(cb, p)),
        )}`,
      );
    return false;
  }
  const { a, rows } = whole;
  console.log(
    `${bold(path_1.basename(file))}${a.namespace || into ? dim(` → ${into ?? a.namespace}`) : ""}`,
  );
  if (rows.length) printRows(rows, (d) => cb.store.shownSignature(d));
  else if (!a.watches.length) console.log(dim("  (no definitions)"));
  printScratchLint(cb, file, a.defs.length ? changedCode(cb, rows) : undefined);
  if (a.watches.length) {
    const results = evaluateCached(cb, a.watches, cb.source([...a.defs, ...a.watches]), fresh);
    for (const w of a.watches) {
      const r = results.get(w.line);
      console.log(
        `\n  ${dim(String(w.line).padStart(3) + " │")} ${w.expression}${r?.cached ? dim("  (cached)") : ""}`,
      );
      console.log(
        `      ${r?.ok ? cyan("⧩") : red("✗")} ${(r?.value ?? "").split("\n").join("\n        ")}`,
      );
    }
  }
  const tests = rows.filter((r) => r.def.kind !== "type" && isTestPath(r.path));
  if (tests.length) {
    const outcomes = runCached(
      cb,
      tests.map((r) => ({ path: r.path, rec: r.def })),
      cb.source([...a.defs, ...a.watches]),
      fresh,
    );
    console.log(`\n  ${dim("tests")}  ${summary(outcomes)}`);
    printOutcomes(outcomes, "    ");
  }
  const pending = rows.filter((r) => r.status !== "identical");
  // (brief, as the MCP server's tools run it, the tool says what saves it)
  if (pending.length && process.env.TCM_BRIEF !== "1") {
    const needsUpdate = pending.some((r) => r.status === "update" || r.status === "conflict");
    console.log(
      dim(
        `\n  run \`tcm ${needsUpdate ? "update" : "add"}\` to save ${pending.length} definition${pending.length > 1 ? "s" : ""}`,
      ),
    );
  }
  return true;
}

uses

used by