syntaxai/tdd.md · main · src / a31_commits.ts

a31_commits.ts 66 lines · 2040 bytes raw
export type Phase = "red" | "green" | "refactor" | "spike" | "init" | "untagged";

export interface ParsedCommit {
  phase: Phase;
  step: string | null;
  subject: string;
}

const PHASE_RE = /^(red|green|refactor|spike)(?:\(([a-z][a-z0-9-]*)\))?:\s*(.*)$/i;

export const parseCommit = (message: string): ParsedCommit => {
  const subject = message.split("\n")[0] ?? "";
  const m = subject.match(PHASE_RE);
  if (m) {
    return {
      phase: m[1]!.toLowerCase() as Phase,
      step: m[2] ?? null,
      subject: m[3] ?? "",
    };
  }
  if (/^Initial commit$/i.test(subject)) {
    return { phase: "init", step: null, subject };
  }
  return { phase: "untagged", step: null, subject };
};

export interface Progress {
  verifiedSteps: Set<string>;
  redCount: number;
  greenCount: number;
  refactorCount: number;
  spikeCount: number;
  untaggedCount: number;
}

// A step counts as "verified" when its red commit is followed by a green
// for the same step. Refactor and untagged commits are tallied separately
// for the score breakdown but don't move verification.
export const computeProgress = (commits: { commit: { message: string } }[]): Progress => {
  const pendingRed = new Set<string>();
  const verifiedSteps = new Set<string>();
  let redCount = 0;
  let greenCount = 0;
  let refactorCount = 0;
  let spikeCount = 0;
  let untaggedCount = 0;
  // Forgejo returns commits newest-first; walk oldest-first to get sequence.
  for (const c of [...commits].reverse()) {
    const p = parseCommit(c.commit.message);
    if (p.phase === "red") {
      redCount++;
      if (p.step) pendingRed.add(p.step);
    } else if (p.phase === "green") {
      greenCount++;
      if (p.step && pendingRed.has(p.step)) verifiedSteps.add(p.step);
    } else if (p.phase === "refactor") {
      refactorCount++;
    } else if (p.phase === "spike") {
      spikeCount++;
    } else if (p.phase === "untagged") {
      untaggedCount++;
    }
  }
  return { verifiedSteps, redCount, greenCount, refactorCount, spikeCount, untaggedCount };
};