diff --git a/Makefile b/Makefile index cf22ffe..70551ae 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: factory-maintain lab next progress prove-homework doctor dev check check-affected prove levels claude smoke hooks eval factory exercise +.PHONY: fast factory-maintain lab next progress prove-homework doctor dev check check-affected prove levels claude smoke hooks eval factory exercise doctor: @node -v | grep -qE '^v2[2-9]' && echo "ok node $$(node -v)" || (echo "need Node 22+" && exit 1) @@ -22,6 +22,12 @@ check: check-affected: npx tsx scripts/check-affected.ts $(if $(BASE),--base $(BASE)) +# Quickest local read on the first red CI step: lint, types, then only the affected checks. make check stays the gate. +fast: + npm run lint + npm run typecheck + npx tsx scripts/check-affected.ts --base origin/main + prove: npm run prove -- $(LEVEL) diff --git a/levels.json b/levels.json index 9c18370..c3cef48 100644 --- a/levels.json +++ b/levels.json @@ -168,6 +168,7 @@ "scripts/impact.ts", "scripts/impact.test.ts", "scripts/check-affected.ts", + "scripts/fast.test.ts", "tests/structure/impact.test.ts", "scripts/dependencies.ts", "tests/structure/dependency-drift.test.ts", diff --git a/scripts/fast.test.ts b/scripts/fast.test.ts new file mode 100644 index 0000000..6e7c6f9 --- /dev/null +++ b/scripts/fast.test.ts @@ -0,0 +1,38 @@ +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; + +const makefile = readFileSync("Makefile", "utf8"); + +// The tab-indented lines of one target's recipe. +function recipe(target: string): string[] { + const lines = makefile.split("\n"); + const start = lines.indexOf(`${target}:`); + if (start < 0) return []; + const body: string[] = []; + for (const line of lines.slice(start + 1)) { + if (!line.startsWith("\t")) break; + body.push(line.trim()); + } + return body; +} + +describe("make fast", () => { + it("is declared phony", () => { + const phony = makefile.split("\n").find((l) => l.startsWith(".PHONY:")) ?? ""; + expect(phony.split(/\s+/)).toContain("fast"); + }); + + it("runs lint, typecheck, then the affected checks against origin/main", () => { + expect(recipe("fast")).toEqual([ + "npm run lint", + "npm run typecheck", + "npx tsx scripts/check-affected.ts --base origin/main", + ]); + }); + + it("is not called from check, which stays the gate", () => { + const check = recipe("check"); + expect(check.length).toBeGreaterThan(0); + expect(check.join("\n")).not.toMatch(/\bfast\b/); + }); +});