Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions tools/hygiene/audit-backlog-status-drift.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import {
extractPrimaryArtifacts,
parseFrontmatter,
findDriftCandidates,
detectRepoRoot,
type BacklogRow,
} from "./audit-backlog-status-drift";
import { existsSync } from "node:fs";
import { join } from "node:path";

describe("parseFrontmatter", () => {
test("reads status field from YAML frontmatter", () => {
Expand Down Expand Up @@ -256,3 +259,23 @@ describe("findDriftCandidates", () => {
expect(findDriftCandidates(rows)).toEqual([]);
});
});

describe("detectRepoRoot", () => {
test("returns a directory path containing the audit tool itself", () => {
// The repo root should always contain tools/hygiene/audit-backlog-status-drift.ts
// (this file). If detection works, that path resolves.
const root = detectRepoRoot();
expect(typeof root).toBe("string");
expect(root.length).toBeGreaterThan(0);
expect(existsSync(join(root, "tools/hygiene/audit-backlog-status-drift.ts"))).toBe(true);
});

test("returns repo root from cwd inside the repo (not just current cwd)", () => {
// Verifies invariant: regardless of what cwd test runner uses, detectRepoRoot
// returns the repo root (via git rev-parse). Confirms cwd-independence.
const root = detectRepoRoot();
// Repo root should contain canonical top-level files.
expect(existsSync(join(root, "CLAUDE.md"))).toBe(true);
expect(existsSync(join(root, "docs/backlog"))).toBe(true);
});
});
30 changes: 30 additions & 0 deletions tools/hygiene/audit-backlog-status-drift.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,25 @@

import { readdirSync, readFileSync, existsSync } from "node:fs";
import { join } from "node:path";
import { execFileSync } from "node:child_process";

/**
* Detect the repo root via `git rev-parse --show-toplevel`. Falls back to the
* current working directory if git isn't available or this isn't a git repo.
*
* Invariant: relative-path reads inside this tool resolve against the repo
* root regardless of where the tool was invoked from. Without this, running
* the tool from any subdirectory would false-negative every existsSync check.
*
* Per B-0557 slice 3.
*/
export function detectRepoRoot(): string {
try {
return execFileSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf-8" }).trim();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Silence git stderr in repo-root detection fallback

When this tool is invoked from a directory that is not a Git worktree, execFileSync("git", ["rev-parse", "--show-toplevel"]) throws as expected, but Git’s fatal message is still emitted to stderr before the catch fallback runs. That means a successful run can now produce unexpected stderr noise (including in --json mode), which can break automation that treats any stderr output as a failed check. This behavior is introduced by the new root-detection path and should be suppressed when the fallback path is intentional.

Useful? React with 👍 / 👎.

} catch {
return process.cwd();
}
}

type PrimarySectionMatcher = RegExp;

Expand Down Expand Up @@ -244,6 +263,17 @@ function reportJson(candidates: readonly BacklogRow[]): void {
}

function main(): number {
// chdir to repo root so subsequent relative-path reads/existence-checks
// work regardless of invocation cwd. Per B-0557 slice 3.
try {
process.chdir(detectRepoRoot());
Comment thread
AceHack marked this conversation as resolved.
} catch (err) {
process.stderr.write(
`audit-backlog-status-drift: unable to chdir to repo root: ${(err as Error).message}\n`,
);
return 64;
}

const args = process.argv.slice(2);

const KNOWN_FLAGS = new Set(["--json", "--check", "--help", "-h"]);
Expand Down
Loading