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
8 changes: 0 additions & 8 deletions .2119/verdicts/REQ-003.2.2--0e2870584d77.json

This file was deleted.

8 changes: 8 additions & 0 deletions .2119/verdicts/REQ-003.2.2--204eb12e5588.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"reviewId": "REQ-003.2.2--204eb12e5588",
"requirementId": "REQ-003.2.2",
"hash": "204eb12e5588",
"verdict": "pass",
"summary": "writeVerdict emits readable JSON under .2119/verdicts; init ignores only .2119/reviews, and Git confirms verdict JSON is tracked and not ignored for PR audit",
"timestamp": "2026-07-21T23:53:11.020Z"
}
8 changes: 8 additions & 0 deletions .2119/verdicts/REQ-009.1.1--425359da4a3d.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"reviewId": "REQ-009.1.1--425359da4a3d",
"requirementId": "REQ-009.1.1",
"hash": "425359da4a3d",
"verdict": "pass",
"summary": "Real-CLI test creates an audit with its sole judgment already passed and directly asserts generated evidence plus exit 0.",
"timestamp": "2026-07-21T23:39:48.163Z"
}
8 changes: 8 additions & 0 deletions .2119/verdicts/REQ-009.1.2--6fd6ac75a9da.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"reviewId": "REQ-009.1.2--6fd6ac75a9da",
"requirementId": "REQ-009.1.2",
"hash": "6fd6ac75a9da",
"verdict": "pass",
"summary": "Real-CLI test creates a passed audit target alongside one pending judgment and directly asserts audit generation plus exit 1.",
"timestamp": "2026-07-21T23:39:48.162Z"
}
19 changes: 19 additions & 0 deletions specs/REQ-009-audit-exit-status.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# REQ-009: Audit Exit Status

## Overview

`2119 review --audit` is a discretionary quality-assurance command: it writes adversarial
instructions for requirements that already have passing verdicts so an operator can challenge
those verdicts with a fresh reviewer. Today, a successful audit-only run exits 1 even though no
required judgment review is pending. That makes a healthy periodic audit sweep look like a gate
failure to shell scripts and CI, and makes the `audit: always` configuration costly to automate.

Audit generation should report whether the command succeeded; the existing non-zero signal for
required, pending judgment reviews remains unchanged.

## Requirements

### REQ-009.1: Optional audit success

1. When `2119 review` generates one or more adversarial audit instruction files and no judgment review is pending, the command MUST exit 0.
2. When any judgment review is pending, `2119 review` MUST retain its non-zero exit status even if it also generates adversarial audit instruction files.
2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ switch (command) {
}
if (tasks.length === 0) {
console.log("review: no pending reviews; dispatch the audits above to fresh-context reviewers.");
process.exit(1);
break;
}
console.log(
`${tasks.length} judgment review(s) pending. Dispatch each to a FRESH-CONTEXT reviewer\n` +
Expand Down
29 changes: 29 additions & 0 deletions tests/rigor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,35 @@ describe("deterministic rigor (0.6)", () => {
expect(existsSync(join(root, `.2119/reviews/${id}.audit.md`))).toBe(true);
});

// 2119: REQ-009.1.1
it("exits zero when an audit-only review run generates instructions", () => {
const root = fixture();
const id = run(root, ["review"]).stdout.match(/FIX-001\.1\.1--[0-9a-f]{12}/)![0];
expect(run(root, ["pass", id, "--summary", "asserts spin behavior"]).status).toBe(0);

const result = run(root, ["review", "--audit"]);
expect(existsSync(join(root, `.2119/reviews/${id}.audit.md`))).toBe(true);
expect(result.stdout).toContain("adversarial audit(s) generated");
expect(result.status).toBe(0);
});

// 2119: REQ-009.1.2
it("retains a non-zero exit when audits and pending reviews coexist", () => {
const root = fixture(`${SPEC}2. The widget MUST stop.\n`);
writeFileSync(
join(root, "tests/widget.test.js"),
"// 2119: FIX-001.1.1\ntest('spins', () => {})\n// 2119: FIX-001.1.2\ntest('stops', () => {})\n",
);
const initial = run(root, ["review"]).stdout;
const passingId = initial.match(/FIX-001\.1\.1--[0-9a-f]{12}/)![0];
expect(run(root, ["pass", passingId, "--summary", "asserts spin behavior"]).status).toBe(0);

const result = run(root, ["review", "--audit"]);
expect(existsSync(join(root, `.2119/reviews/${passingId}.audit.md`))).toBe(true);
expect(result.stdout).toContain("judgment review(s) pending");
expect(result.status).toBe(1);
});

// 2119: REQ-004.1.10
it("session-start injects an upgrade notice from a cached probe", async () => {
const root = fixture();
Expand Down
Loading