Archilyzer · Source

archilyzer

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

commit bd0565c54944b3ff6c5c42220176b9cf8cecb67a
parent 6fcadfffea368687ee00a9ed64ed3eb6e01d8ad5
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Wed, 30 Sep 2026 09:07:52 -0400

common: `archilyzer doctor` reports stagit beside git-filter-repo — its path, or not found with the install line; a STAGIT_BIN that names nothing is a warning, never a failure

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

Diffstat:
Mcommon/bin/doctor.test.ts | 25++++++++++++++++++++-----
Mcommon/bin/doctor.ts | 25+++++++++++++++++++++----
2 files changed, 41 insertions(+), 9 deletions(-)

diff --git a/common/bin/doctor.test.ts b/common/bin/doctor.test.ts @@ -244,12 +244,19 @@ test("the source publish block: the tools, the operator files by count and mode sourceTools: async () => tools, }); // A clone that never publishes: notes, not warnings. - let r = await collectDoctorReport(deps({ filterRepo: null, gitleaks: null })); + let r = await collectDoctorReport(deps({ filterRepo: null, gitleaks: null, stagit: null })); assert.equal(r.ok, true, renderDoctorReport(r)); - for (const id of ["filter-repo", "gitleaks", "scrub rules", "denylist", "published"]) { + for (const id of ["filter-repo", "gitleaks", "stagit", "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`/); + // stagit, beside git-filter-repo: where it is, or not found and how to install it. + assert.equal( + r.checks.find((x) => x.id === "stagit")!.detail, + "not found (PATH, ~/.local/bin) — the source is published without its history pages (/source/git/); install it once: git clone git://git.codemadness.org/stagit && make -C stagit && cp stagit/stagit ~/.local/bin/", + ); + const order = r.checks.filter((x) => x.section === "source publish").map((x) => x.id); + assert.equal(order.indexOf("stagit"), order.indexOf("filter-repo") + 1, "beside git-filter-repo"); // The operator's files exist (one readable by others), pipx only, a publish. mkdirSync(path.dirname(c.paths.sourceScrubFile), { recursive: true }); @@ -267,11 +274,13 @@ test("the source publish block: the tools, the operator files by count and mode audit: { objects: 1, commits: 1, gitleaks: "clean" }, tools: {}, })); const before = tree(c.root); - r = await collectDoctorReport(deps({ filterRepo: { via: "pipx", version: "1.15.0" }, gitleaks: { version: "8.28.0" } })); + r = await collectDoctorReport(deps({ filterRepo: { via: "pipx", version: "1.15.0" }, gitleaks: { version: "8.28.0" }, stagit: "/opt/stagit/stagit" })); 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, "stagit"), "ok"); + assert.equal(r.checks.find((x) => x.id === "stagit")!.detail, "/opt/stagit/stagit"); 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"); @@ -282,8 +291,14 @@ test("the source publish block: the tools, the operator files by count and mode 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 })); + r = await collectDoctorReport(deps({ filterRepo: { via: "git", version: "a40bce548d2c" }, gitleaks: null, stagit: null })); assert.equal(status(r, "filter-repo"), "ok"); + + // A STAGIT_BIN that names nothing: a warning, never a failure. + r = await collectDoctorReport({ ...deps({ filterRepo: null, gitleaks: null, stagit: null }), env: { PATH: c.bin, STAGIT_BIN: "/nowhere/stagit" } }); + assert.equal(status(r, "stagit"), "warn"); + assert.match(r.checks.find((x) => x.id === "stagit")!.detail, /^not found: STAGIT_BIN=\/nowhere\/stagit names no executable/); + assert.equal(r.ok, true); }); // A stored icon the checker refuses is named by file and label with its @@ -309,7 +324,7 @@ test("social icons: a refused stored icon is named by file and label, not its ma portInUse: async () => false, portBlock: async () => null, umtoolTools: async () => null, - sourceTools: async () => ({ filterRepo: null, gitleaks: null }), + sourceTools: async () => ({ filterRepo: null, gitleaks: null, stagit: null }), }); const line = report.checks.find((x) => x.section === "social icons")!; assert.equal(line.status, "warn"); diff --git a/common/bin/doctor.ts b/common/bin/doctor.ts @@ -74,10 +74,12 @@ export type DoctorDeps = { sourceTools?: () => Promise<SourceTools>; }; -// Which git-filter-repo `archilyzer source publish` would run, and gitleaks. +// Which git-filter-repo `archilyzer source publish` would run, gitleaks, and +// the stagit that renders the history pages (its path, or null). export type SourceTools = { filterRepo: { via: "git"; version: string } | { via: "pipx"; version: string } | null; gitleaks: { version: string } | null; + stagit: string | null; }; const MIN_NODE = [20, 9, 0] as const; // next 16's engines field @@ -291,7 +293,7 @@ export async function collectDoctorReport(deps: DoctorDeps): Promise<DoctorRepor // are never printed. const SP = "source publish"; const src = await import("../publish/source"); - const tools = await (deps.sourceTools ?? (() => probeSourceTools(env)))(); + const tools = await (deps.sourceTools ?? (() => probeSourceTools(env, paths)))(); const scrubFile = paths.sourceScrubFile; const denylistFile = paths.sourceDenylistFile; const intends = [scrubFile, denylistFile].some((f) => f && existsSync(f)); @@ -304,6 +306,18 @@ export async function collectDoctorReport(deps: DoctorDeps): Promise<DoctorRepor add(SP, "filter-repo", intends ? "warn" : "info", `neither git-filter-repo nor pipx — \`archilyzer build homepage\` refuses; install: \`${src.FILTER_REPO_INSTALL}\``); } + // stagit renders the history pages (/source/git/); without it the publish + // goes on without them. A STAGIT_BIN that names nothing is a warning. + const hist = await import("../publish/sourceHistory"); + if (tools.stagit) { + add(SP, "stagit", "ok", tools.stagit); + } else if (env.STAGIT_BIN) { + add(SP, "stagit", "warn", + `not found: STAGIT_BIN=${env.STAGIT_BIN} names no executable — the source is published without its history pages (/source/git/)`); + } else { + add(SP, "stagit", "info", + `not found (PATH, ~/.local/bin) — the source is published without its history pages (/source/git/); install it once: ${hist.STAGIT_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 [ @@ -518,8 +532,9 @@ 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> { +// version (never `pipx run`, which would download). gitleaks likewise. stagit +// has no version flag: it is looked up the way the publish looks it up. +async function probeSourceTools(env: NodeJS.ProcessEnv, paths: Paths): Promise<SourceTools> { const version = async (bin: string, args: string[]): Promise<string | null> => { try { const { stdout, stderr } = await execFileP(bin, args, { env, timeout: 10_000 }); @@ -531,9 +546,11 @@ async function probeSourceTools(env: NodeJS.ProcessEnv): Promise<SourceTools> { const git = await version("git", ["filter-repo", "--version"]); const pipx = git === null ? await version("pipx", ["--version"]) : null; const leaks = await version("gitleaks", ["version"]); + const { resolveStagit } = await import("../publish/sourceHistory"); return { filterRepo: git !== null ? { via: "git", version: git } : pipx !== null ? { via: "pipx", version: pipx } : null, gitleaks: leaks !== null ? { version: leaks } : null, + stagit: resolveStagit(paths.stagitBin ?? "stagit", env), }; }