Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 9aa6fc738f8a1cfd6ec88c837c9e9131a5967e27
parent 38d9ca564e16f5189815cc76f879121bfba66440
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon, 28 Sep 2026 13:12:09 -0400

common: build homepage publishes the source; `source publish|audit` rows; doctor's source block

buildHomepage runs publishSource between compose and `next build`, so the CLI,
the runbooks' home scripts and the /sites homepage jobs all produce it, and a
refusal fails the build before `next build`. `--no-source` (CLI only) removes
the previously published source instead of shipping one audited against
older rules. `archilyzer source publish [--force] [--check] [--keep-scratch]`
and `source audit [<git dir>]` are table rows. `archilyzer doctor` reports
which filter-repo would run (or the install line), gitleaks, the two operator
files by rule count and mode (never their contents) and the last publish —
never a failure.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Mcommon/bin/_cli.test.ts | 34++++++++++++++++++++++++++++++++++
Mcommon/bin/archilyzer.ts | 36+++++++++++++++++++++++++++++++++---
Mcommon/bin/doctor.test.ts | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcommon/bin/doctor.ts | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcommon/publish/build.test.ts | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcommon/publish/build.ts | 30++++++++++++++++++++++++++++--
6 files changed, 286 insertions(+), 5 deletions(-)

diff --git a/common/bin/_cli.test.ts b/common/bin/_cli.test.ts @@ -314,6 +314,40 @@ test("release show says the latest release, its date and what is pending, and wh assert.equal(formatAllLine([editor]), null); }); +// --- archilyzer source (release 12 slice R) -------------------------------- + +test("usage lists source publish, source audit and build homepage's --no-source", () => { + const u = usage(COMMANDS); + assert.match(u, /archilyzer source publish\s+\[--force\] \[--check\] \[--keep-scratch\] the scrubbed git mirror/); + assert.match(u, /archilyzer source audit\s+\[<git dir>\] the denied-literal gate/); + assert.match(u, /archilyzer build homepage\s+\[--no-source\] compose \+ source publish \+ next build/); +}); + +test("source publish takes its three booleans and nothing else; none swallows a word", () => { + const hit = resolveCommand(COMMANDS, ["source", "publish"]); + assert.deepEqual(hit?.command.path, ["source", "publish"]); + const parsed = parseArgv(["source", "publish", "--check", "--force", "--keep-scratch"], booleanFlags(COMMANDS)); + assert.deepEqual(parsed, { + positionals: ["source", "publish"], + flags: { check: true, force: true, "keep-scratch": true }, + }); + assert.equal(argumentProblem(hit!.command, parsed.flags, []), null); + assert.match(argumentProblem(hit!.command, { preview: "x" }, [])!, /unknown flag --preview \(accepts --force, --check, --keep-scratch\)/); + assert.match(argumentProblem(hit!.command, {}, ["extra"])!, /unexpected argument "extra"/); +}); + +test("source audit takes one git dir; build homepage takes --no-source only", () => { + const audit = resolveCommand(COMMANDS, ["source", "audit", "/tmp/clone/.git"]); + assert.deepEqual(audit?.rest, ["/tmp/clone/.git"]); + assert.equal(argumentProblem(audit!.command, {}, audit!.rest), null); + assert.match(argumentProblem(audit!.command, {}, ["a", "b"])!, /unexpected argument "b"/); + const home = resolveCommand(COMMANDS, ["build", "homepage"]); + const parsed = parseArgv(["build", "homepage", "--no-source"], booleanFlags(COMMANDS)); + assert.deepEqual(parsed, { positionals: ["build", "homepage"], flags: { "no-source": true } }); + assert.equal(argumentProblem(home!.command, parsed.flags, []), null); + assert.match(argumentProblem(home!.command, { force: true }, [])!, /unknown flag --force \(accepts --no-source\)/); +}); + // ── passthrough commands (one-core Phase 4 slice 3) ───────────────────────── test("a passthrough command gets every word after its path, verbatim and unchecked", async () => { diff --git a/common/bin/archilyzer.ts b/common/bin/archilyzer.ts @@ -137,15 +137,45 @@ export const COMMANDS: Command[] = [ }, { path: ["build", "homepage"], - usage: "compose + next build in homepage/ (reads the index as it stands)", - run: async () => { + usage: + "[--no-source] compose + source publish + next build in homepage/ (reads the index as it stands); --no-source removes the published source instead", + flags: { "no-source": "boolean" }, + run: async ({ flags }) => { const { buildHomepage } = await import("../publish/build"); - const code = await buildHomepage({ signal: interrupted() }); + const code = await buildHomepage({ + signal: interrupted(), + skipSource: flags["no-source"] === true, + }); if (code !== 0) console.error(`build homepage: failed (exit ${code})`); return code; }, }, { + path: ["source", "publish"], + usage: + "[--force] [--check] [--keep-scratch] the scrubbed git mirror, raw tree and tarball into homepage/public, behind the denied-literal gate (--check: audit and count, write nothing)", + flags: { force: "boolean", check: "boolean", "keep-scratch": "boolean" }, + run: async ({ flags }) => { + const { publishSource } = await import("../publish/source"); + return publishSource({ + signal: interrupted(), + force: flags.force === true, + check: flags.check === true, + keepScratch: flags["keep-scratch"] === true, + }); + }, + }, + { + path: ["source", "audit"], + usage: + "[<git dir>] the denied-literal gate (+ gitleaks) over a git dir; default the published homepage/public/source/archilyzer.git", + maxPositionals: 1, + run: async ({ positionals }) => { + const { auditSource } = await import("../publish/source"); + return auditSource({ signal: interrupted(), gitDir: positionals[0] }); + }, + }, + { path: ["deploy", "site"], usage: "<id> [--preview <branch>] ship the site built in export/out to its Pages project (default id: SITE_ID)", diff --git a/common/bin/doctor.test.ts b/common/bin/doctor.test.ts @@ -56,6 +56,8 @@ function checkout(): { root: string; bin: string; paths: Paths } { whisperModel: path.join(root, "models", "ggml-base.en.bin"), parakeetModel: "", parakeetCliBin: "parakeet-cli", + sourceScrubFile: path.join(root, ".config", "source-scrub.txt"), + sourceDenylistFile: path.join(root, ".config", "source-denylist.txt"), } as unknown as Paths; return { root, bin, paths }; } @@ -229,3 +231,56 @@ test("umtool's table and the port block are reported, never failed", async () => assert.match(r.checks.find((x) => x.id === "EDITOR_PORT")!.detail, /^3301 in use/); assert.equal(r.ok, true); }); + +test("the source publish block: the tools, the operator files by count and mode (never their contents), the last publish — never a failure", async () => { + const c = checkout(); + const deps = (tools: Awaited<ReturnType<NonNullable<Parameters<typeof collectDoctorReport>[0]["sourceTools"]>>>) => ({ + env: { PATH: c.bin }, + paths: c.paths, + nodeVersion: "22.0.0", + portBlock: async () => null, + portInUse: async () => false, + umtoolTools: async () => null, + sourceTools: async () => tools, + }); + // A clone that never publishes: notes, not warnings. + let r = await collectDoctorReport(deps({ filterRepo: null, gitleaks: null })); + assert.equal(r.ok, true, renderDoctorReport(r)); + for (const id of ["filter-repo", "gitleaks", "scrub rules", "denylist", "published"]) { + assert.equal(status(r, id), "info", id); + } + assert.match(r.checks.find((x) => x.id === "filter-repo")!.detail, /install: `pipx install git-filter-repo`/); + + // The operator's files exist (one readable by others), pipx only, a publish. + mkdirSync(path.dirname(c.paths.sourceScrubFile), { recursive: true }); + writeFileSync(c.paths.sourceScrubFile, "# mine\nPLANTED-A==>x\n\nPLANTED-B==>y\n"); + chmodSync(c.paths.sourceScrubFile, 0o600); + writeFileSync(c.paths.sourceDenylistFile, "PLANTED-SECRET\n"); + chmodSync(c.paths.sourceDenylistFile, 0o644); + const pub = path.join(c.root, "homepage", "public", "source"); + mkdirSync(pub, { recursive: true }); + writeFileSync(path.join(pub, "manifest.json"), JSON.stringify({ + version: 1, generatedAt: "2026-09-28T12:00:00.000Z", branch: "main", + sourceCommit: "1".repeat(40), mirrorHead: "2".repeat(40), subject: "s", files: 2400, bytes: 1, + mirror: {}, tree: {}, tarball: { href: "/downloads/archilyzer-source.tar.gz", bytes: 1, sha256: "3".repeat(64) }, + audit: {}, tools: {}, + })); + const before = tree(c.root); + r = await collectDoctorReport(deps({ filterRepo: { via: "pipx", version: "1.15.0" }, gitleaks: { version: "8.28.0" } })); + assert.equal(r.ok, true, renderDoctorReport(r)); + assert.equal(status(r, "filter-repo"), "warn"); + assert.match(r.checks.find((x) => x.id === "filter-repo")!.detail, /pipx run --spec git-filter-repo==2\.47\.0/); + assert.equal(status(r, "gitleaks"), "ok"); + assert.equal(status(r, "scrub rules"), "ok"); + assert.match(r.checks.find((x) => x.id === "scrub rules")!.detail, /\(2 rules, mode 600\)$/); + assert.equal(status(r, "denylist"), "warn"); + assert.match(r.checks.find((x) => x.id === "denylist")!.detail, /\(1 literal, mode 644\) — readable by others: chmod 600/); + assert.match(r.checks.find((x) => x.id === "published")!.detail, /^main 111111111111 as 222222222222, 2026-09-28T12:00:00\.000Z \(2400 files\)/); + const text = renderDoctorReport(r); + assert.match(text, /\nsource publish\n/); + assert.ok(!/PLANTED/.test(text), "the doctor never prints an operator file's contents"); + assert.deepEqual(tree(c.root), before); + + r = await collectDoctorReport(deps({ filterRepo: { via: "git", version: "a40bce548d2c" }, gitleaks: null })); + assert.equal(status(r, "filter-repo"), "ok"); +}); diff --git a/common/bin/doctor.ts b/common/bin/doctor.ts @@ -70,6 +70,14 @@ export type DoctorDeps = { portBlock?: () => Promise<{ label: string; ports: Record<string, string> } | null>; // umtool's report-pipeline table, or null when there is no umtool here. umtoolTools?: () => Promise<ToolSpec[] | null>; + // The source publish's tools. Default: probeSourceTools(env). + sourceTools?: () => Promise<SourceTools>; +}; + +// Which git-filter-repo `archilyzer source publish` would run, and gitleaks. +export type SourceTools = { + filterRepo: { via: "git"; version: string } | { via: "pipx"; version: string } | null; + gitleaks: { version: string } | null; }; const MIN_NODE = [20, 9, 0] as const; // next 16's engines field @@ -254,6 +262,50 @@ export async function collectDoctorReport(deps: DoctorDeps): Promise<DoctorRepor } } + // ── source publish ─────────────────────────────────────────────────────── + // `archilyzer build homepage` runs it (common/publish/source.ts). Never a + // failure: a checkout that never publishes the homepage is not broken. A + // WARN is a machine that means to (its operator files exist) and cannot. + // The operator files are stat'd and their RULES COUNTED — their contents + // are never printed. + const SP = "source publish"; + const src = await import("../publish/source"); + const tools = await (deps.sourceTools ?? (() => probeSourceTools(env)))(); + const scrubFile = paths.sourceScrubFile; + const denylistFile = paths.sourceDenylistFile; + const intends = [scrubFile, denylistFile].some((f) => f && existsSync(f)); + if (tools.filterRepo?.via === "git") { + add(SP, "filter-repo", "ok", `git filter-repo ${tools.filterRepo.version}`); + } else if (tools.filterRepo?.via === "pipx") { + add(SP, "filter-repo", intends ? "warn" : "info", + `not installed; \`pipx run --spec ${src.FILTER_REPO_PIPX_SPEC}\` fetches it at build time (network on first use; pipx ${tools.filterRepo.version}) — \`${src.FILTER_REPO_INSTALL}\` once removes that`); + } else { + add(SP, "filter-repo", intends ? "warn" : "info", + `neither git-filter-repo nor pipx — \`archilyzer build homepage\` refuses; install: \`${src.FILTER_REPO_INSTALL}\``); + } + add(SP, "gitleaks", tools.gitleaks ? "ok" : "info", + tools.gitleaks ? `gitleaks ${tools.gitleaks.version}` : "absent — the gate skips the secret scan with a WARNING (the literal audit still runs)"); + for (const [id, file, unit] of [ + ["scrub rules", scrubFile, "rule"], + ["denylist", denylistFile, "literal"], + ] as const) { + const st = file ? statOrNull(file) : null; + if (!file || !st) { + add(SP, id, "info", `${file ?? "(unset)"} is missing — \`source publish\` (and so \`build homepage\`) refuses until it exists (PUBLISH.md, "The source mirror")`); + continue; + } + const count = (readOrNull(file) ?? "").split("\n").filter((l) => l.trim() && !l.trim().startsWith("#")).length; + const mode = st.mode & 0o777; + const open = (mode & 0o077) !== 0; + add(SP, id, open ? "warn" : "ok", + `${file} (${count} ${unit}${count === 1 ? "" : "s"}, mode ${mode.toString(8)})${open ? " — readable by others: chmod 600" : ""}`); + } + const publicDir = env.HOMEPAGE_PUBLIC_DIR || path.join(root, "homepage", "public"); + const published = await src.readPublishedManifest(publicDir); + add(SP, "published", "info", published + ? `main ${published.sourceCommit.slice(0, 12)} as ${published.mirrorHead.slice(0, 12)}, ${published.generatedAt} (${published.files} files)` + : `nothing published in ${path.join(publicDir, "source")}`); + // ── ports ──────────────────────────────────────────────────────────────── const P = "ports"; const block = await (deps.portBlock ?? (() => worktreePortBlock(root)))(); @@ -396,6 +448,27 @@ async function worktreePortBlock( } } +// What `source publish` would run, by version flags only: `git filter-repo +// --version` answering 0 is an installed filter-repo; otherwise pipx's own +// version (never `pipx run`, which would download). gitleaks likewise. +async function probeSourceTools(env: NodeJS.ProcessEnv): Promise<SourceTools> { + const version = async (bin: string, args: string[]): Promise<string | null> => { + try { + const { stdout, stderr } = await execFileP(bin, args, { env, timeout: 10_000 }); + return (stdout || stderr).trim().split("\n")[0] ?? ""; + } catch { + return null; + } + }; + const git = await version("git", ["filter-repo", "--version"]); + const pipx = git === null ? await version("pipx", ["--version"]) : null; + const leaks = await version("gitleaks", ["version"]); + return { + filterRepo: git !== null ? { via: "git", version: git } : pipx !== null ? { via: "pipx", version: pipx } : null, + gitleaks: leaks !== null ? { version: leaks } : null, + }; +} + // In use = something accepts a TCP connection on 127.0.0.1. Never binds. function tcpPortInUse(port: number): Promise<boolean> { return new Promise((resolve) => { diff --git a/common/publish/build.test.ts b/common/publish/build.test.ts @@ -1,8 +1,12 @@ import { test } from "node:test"; import assert from "node:assert/strict"; +import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import os from "node:os"; +import path from "node:path"; import type { Paths } from "../lib/paths"; import { HOMEPAGE_PAGES_PROJECT, + buildHomepage, buildHubSteps, buildSiteSteps, hubProjectProblem, @@ -143,6 +147,65 @@ test("homepageOutDir is homepage/out of the checkout", () => { assert.equal(homepageOutDir({ monorepoRoot: "/repo" } as Paths), "/repo/homepage/out"); }); +// buildHomepage's order — compose, the source step, `next build` — over a temp +// homepage/ whose `compose` is `true` and whose `next` only says it ran. +test("buildHomepage: the source step sits between compose and next build; a refusal stops the build; --no-source clears instead", async () => { + const root = mkdtempSync(path.join(os.tmpdir(), "build-homepage-")); + try { + const home = path.join(root, "homepage"); + mkdirSync(path.join(home, "node_modules", ".bin"), { recursive: true }); + writeFileSync( + path.join(home, "package.json"), + JSON.stringify({ name: "fake-homepage", private: true, scripts: { compose: "echo COMPOSED" } }), + ); + const next = path.join(home, "node_modules", ".bin", "next"); + writeFileSync(next, "#!/bin/sh\necho NEXT RAN\n"); + chmodSync(next, 0o755); + const fakePaths = { monorepoRoot: root } as Paths; + + const run = async (o: Parameters<typeof buildHomepage>[0]) => { + const logs: string[] = []; + const code = await buildHomepage({ paths: fakePaths, onLog: (l) => logs.push(l), ...o }); + return { code, logs: logs.join("\n") }; + }; + const calls: string[] = []; + + // A refusal (1) fails the build before next build runs. + let r = await run({ + publishSource: async (p) => { + calls.push(`publish:${p.paths === fakePaths}`); + return 1; + }, + }); + assert.equal(r.code, 1); + assert.match(r.logs, /COMPOSED/); + assert.doesNotMatch(r.logs, /NEXT RAN/); + assert.deepEqual(calls, ["publish:true"]); + + // Published: next build follows. + r = await run({ publishSource: async () => (calls.push("publish"), 0) }); + assert.equal(r.code, 0, r.logs); + assert.ok(r.logs.indexOf("COMPOSED") < r.logs.indexOf("NEXT RAN")); + + // --no-source: the publish is never called; the clear is. + calls.length = 0; + r = await run({ + skipSource: true, + publishSource: async () => { + throw new Error("--no-source must not publish"); + }, + clearSource: async () => { + calls.push("clear"); + }, + }); + assert.equal(r.code, 0, r.logs); + assert.deepEqual(calls, ["clear"]); + assert.match(r.logs, /NEXT RAN/); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + test("deployHomepage refuses a bad preview branch before it looks for a build", async () => { const noBuild = { monorepoRoot: "/nonexistent-repo" } as Paths; await assert.rejects( diff --git a/common/publish/build.ts b/common/publish/build.ts @@ -969,11 +969,37 @@ export async function composeHomepage(opts: PublishOpts = {}): Promise<number> { ]); } -/** composeHomepage, then `next build` in homepage/ (→ homepage/out). */ -export async function buildHomepage(opts: PublishOpts = {}): Promise<number> { +/** + * composeHomepage, then `archilyzer source publish` (the git mirror, the raw + * tree and the tarball into homepage/public — source.ts), then `next build` in + * homepage/ (→ homepage/out). A source REFUSAL fails the build before `next + * build`: nothing is deployed with a stale or missing source, and the redacted + * audit report is in the log. `skipSource` (the CLI's `--no-source`) REMOVES + * the published source instead (the mirror, the tree, the tarball): a copy + * left from an earlier publish was audited against that day's rules, not + * today's, so a build that skips the gate ships none — the pages show their + * empty states. `publishSource` / `clearSource` are the test's seams. + */ +export async function buildHomepage( + opts: PublishOpts & { + skipSource?: boolean; + publishSource?: (o: PublishOpts) => Promise<number>; + clearSource?: (o: PublishOpts) => Promise<void>; + } = {}, +): Promise<number> { const { paths, onLog, signal } = resolved(opts); const code = await composeHomepage({ paths, onLog, signal }); if (code !== 0) return code; + // Loaded lazily: the source step pulls nothing the other publish entry + // points need, and it imports this module's types. + if (opts.skipSource) { + const clear = opts.clearSource ?? (await import("./source")).clearPublishedSource; + await clear({ paths, onLog, signal }); + } else { + const publish = opts.publishSource ?? (await import("./source")).publishSource; + const sourceCode = await publish({ paths, onLog, signal }); + if (sourceCode !== 0 || signal.aborted) return sourceCode || 1; + } return runSteps(onLog, signal, [ { command: "pnpm",