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
172 changes: 172 additions & 0 deletions tools/alignment/audit_clause_coverage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// audit_clause_coverage.test.ts — tests for alignment-clause coverage audit.
//
// B-0058 slice: adds test coverage for the clause-extraction regex,
// the audit() integration function, and the main() CLI arg parsing.
//
// Run: bun test tools/alignment/audit_clause_coverage.test.ts

import { describe, expect, test } from "bun:test";
import {
ALL_CLAUSES,
audit,
extractClauses,
main,
} from "./audit_clause_coverage.ts";

describe("extractClauses", () => {
test("extracts single clause from text", () => {
expect(extractClauses("This references HC-1 explicitly.")).toEqual([
"HC-1",
]);
});

test("extracts multiple clauses in canonical order", () => {
const content = "We cite DIR-3 and HC-2 and SD-5 here.";
const result = extractClauses(content);
expect(result).toEqual(["HC-2", "SD-5", "DIR-3"]);
});

test("deduplicates repeated clauses", () => {
const content = "HC-1 is mentioned, then HC-1 again, then HC-1.";
expect(extractClauses(content)).toEqual(["HC-1"]);
});

test("returns empty array for content with no clauses", () => {
expect(extractClauses("No alignment clauses here.")).toEqual([]);
});

test("does not match partial clause IDs", () => {
expect(extractClauses("HC-0 SD-0 DIR-0 DIR-6 HC-8 SD-10")).toEqual([]);
});

test("matches clauses at word boundaries", () => {
expect(extractClauses("(HC-7) [SD-1] {DIR-5}")).toEqual([
"HC-7",
"SD-1",
"DIR-5",
]);
});

test("returns results ordered by ALL_CLAUSES, not input order", () => {
const content = "DIR-5 SD-9 HC-1";
const result = extractClauses(content);
expect(result).toEqual(["HC-1", "SD-9", "DIR-5"]);
});

test("handles all 21 clauses", () => {
const content = ALL_CLAUSES.join(" ");
expect(extractClauses(content)).toEqual([...ALL_CLAUSES]);
});
});

describe("ALL_CLAUSES", () => {
test("has exactly 21 clauses", () => {
expect(ALL_CLAUSES).toHaveLength(21);
});

test("covers HC-1..HC-7", () => {
for (let i = 1; i <= 7; i++) {
expect(ALL_CLAUSES).toContain(`HC-${String(i)}`);
}
});

test("covers SD-1..SD-9", () => {
for (let i = 1; i <= 9; i++) {
expect(ALL_CLAUSES).toContain(`SD-${String(i)}`);
}
});

test("covers DIR-1..DIR-5", () => {
for (let i = 1; i <= 5; i++) {
expect(ALL_CLAUSES).toContain(`DIR-${String(i)}`);
}
});
});

describe("audit() integration", () => {
test("returns a valid AuditResult shape", () => {
const result = audit();
expect(result.schema).toBe("alignment-clause-coverage-v2");
Comment on lines +88 to +89
expect(typeof result.totalSurfaces).toBe("number");
expect(typeof result.totalWithZero).toBe("number");
expect(result.totalClauses).toBe(ALL_CLAUSES.length);
expect(Array.isArray(result.surfaces)).toBe(true);
expect(Array.isArray(result.uncitedClauses)).toBe(true);
});

test("finds at least one surface", () => {
const result = audit();
expect(result.totalSurfaces).toBeGreaterThan(0);
});

test("every surface has expected fields", () => {
const result = audit();
for (const s of result.surfaces) {
expect(["skill", "agent", "backlog"]).toContain(s.kind);
expect(typeof s.name).toBe("string");
expect(s.name.length).toBeGreaterThan(0);
expect(typeof s.path).toBe("string");
expect(Array.isArray(s.clausesCited)).toBe(true);
expect(typeof s.clauseCount).toBe("number");
expect(s.clauseCount).toBe(s.clausesCited.length);
}
});

test("totalWithZero equals count of zero-citation surfaces", () => {
const result = audit();
const zeroCount = result.surfaces.filter(
(s) => s.clauseCount === 0,
).length;
expect(result.totalWithZero).toBe(zeroCount);
});

test("uncitedClauses are a subset of ALL_CLAUSES", () => {
const result = audit();
for (const c of result.uncitedClauses) {
expect(ALL_CLAUSES).toContain(c);
}
});

test("cited clauses in surfaces are all valid clause IDs", () => {
const result = audit();
for (const s of result.surfaces) {
for (const c of s.clausesCited) {
expect(ALL_CLAUSES).toContain(c);
}
}
});
});

describe("main() CLI", () => {
test("returns 0 with no args", () => {
expect(main([])).toBe(0);
});
Comment on lines +140 to +143

test("returns 0 with --help", () => {
expect(main(["--help"])).toBe(0);
});

test("returns 0 with --json", () => {
expect(main(["--json"])).toBe(0);
});

test("returns 0 with --md", () => {
expect(main(["--md"])).toBe(0);
});

test("returns 2 for unknown arg", () => {
expect(main(["--bad-flag"])).toBe(2);
});

test("returns 2 when --gate has no value", () => {
expect(main(["--gate"])).toBe(2);
});

test("returns 0 with --gate 0 (trivially satisfied)", () => {
expect(main(["--gate", "0"])).toBe(0);
});

test("returns 1 with --gate 999 (impossible threshold)", () => {
expect(main(["--gate", "999"])).toBe(1);
});
});
4 changes: 2 additions & 2 deletions tools/alignment/audit_clause_coverage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { spawnSync } from "node:child_process";

type AuditExitCode = 0 | 1 | 2;

const ALL_CLAUSES: readonly string[] = [
export const ALL_CLAUSES: readonly string[] = [
"HC-1", "HC-2", "HC-3", "HC-4", "HC-5", "HC-6", "HC-7",
"SD-1", "SD-2", "SD-3", "SD-4", "SD-5", "SD-6", "SD-7", "SD-8", "SD-9",
"DIR-1", "DIR-2", "DIR-3", "DIR-4", "DIR-5",
Expand Down Expand Up @@ -113,7 +113,7 @@ function parseArgs(argv: readonly string[]): ParseResult {
return { kind: "args", args: state };
}

function extractClauses(content: string): readonly string[] {
export function extractClauses(content: string): readonly string[] {
const found = new Set<string>();
const re = /\b(HC-[1-7]|SD-[1-9]|DIR-[1-5])\b/g;
let match: RegExpExecArray | null;
Expand Down
50 changes: 50 additions & 0 deletions tools/alignment/audit_clause_drift.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// audit_clause_drift.test.ts — tests for alignment-clause drift detector.
//
// B-0058 slice: adds test coverage for the drift detector's CLI
// interface and baseline no-drift assertion.
//
// Run: bun test tools/alignment/audit_clause_drift.test.ts

import { describe, expect, test } from "bun:test";
import { main } from "./audit_clause_drift.ts";

describe("main() CLI", () => {
test("returns 0 with --help", () => {
expect(main(["--help"])).toBe(0);
});

test("returns 2 for unknown arg", () => {
expect(main(["--bad-flag"])).toBe(2);
});

test("returns 2 when --base has no value", () => {
expect(main(["--base"])).toBe(2);
});

test("returns 2 when --head has no value", () => {
expect(main(["--head"])).toBe(2);
});
});

describe("no-drift baseline", () => {
test("HEAD vs HEAD shows zero drift", () => {
const code = main(["--base", "HEAD", "--head", "HEAD"]);
expect(code).toBe(0);
});

test("HEAD vs HEAD with --json returns 0", () => {
const code = main(["--base", "HEAD", "--head", "HEAD", "--json"]);
expect(code).toBe(0);
});

test("HEAD vs HEAD with --md returns 0", () => {
const code = main(["--base", "HEAD", "--head", "HEAD", "--md"]);
expect(code).toBe(0);
});
});

describe("default base ref", () => {
test("returns 0 with no args (defaults to main vs HEAD)", () => {
expect(main([])).toBe(0);
Comment on lines +11 to +48
});
});
Loading