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: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,13 @@ Accuracy rules the reviewer follows:
- Prisma/SQL foreign keys must match the referenced column type (for example `UUID` vs `TEXT`).
- Every finding must sit on a real changed line.

### Project brief (`HARE.md`)
### Project brief (`.github/claude/SYSTEM_PROFILE.md`)

Hare does not clone your whole tree. Put a short **`HARE.md`** at the repo root (see [docs/HARE.example.md](docs/HARE.example.md)). It is loaded **before** the diff review and treated as project law.
On each review Hare tries to load **`.github/claude/SYSTEM_PROFILE.md`** from the PR head. If it exists, that file is the project brief (what this repo is, CI stamps, study hash). If it is missing, Hare continues with the normal diff-only review — it does not fail.

Do **not** point Hare at CLAUDE workspace profiles or `.cursor/rules/constitution.mdc` — those drift and are often not even in the git repo. Write facts about the current tree only.
Optional extras, loaded after the profile if present: `HARE.md`, `.hare.md`, `docs/HARE.md`.

Hare does **not** read CLAUDE workspace profiles or `.cursor/rules/constitution.mdc`.

---

Expand Down
27 changes: 4 additions & 23 deletions docs/HARE.example.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,6 @@
# Hare review brief (example)
# Optional extra brief

Commit this as `HARE.md` at the **repo root** of the project under review.
Hare loads it on every PR. Do **not** paste CLAUDE workspace profiles or constitution.mdc here.
Hare first loads **`.github/claude/SYSTEM_PROFILE.md`** from the repo under review.
If that file is missing, the review still runs (diff + guessed context only).

Keep it under ~80 lines. Facts about **this git tree**, not the org.

```md
# Hare review brief — <repo>

## What this repo is
- …

## What this repo is not
- …

## Generated / gitignored
- Do not flag missing files that are produced at build (list them).

## Must-hold invariants
- …

## Do not file
- …
```
`HARE.md` is optional extra. Do not paste constitution.mdc here.
27 changes: 22 additions & 5 deletions src/lib/hare/briefs.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
/** Project brief files — loaded first. Never constitution.mdc / CLAUDE workspace profiles. */
export const PROJECT_BRIEF_PATHS = ["HARE.md", ".hare.md", "docs/HARE.md"];
/**
* Project brief files, tried in order. Missing files are skipped.
* `.github/claude/SYSTEM_PROFILE.md` is the Joe Solutions in-repo stamp/profile.
*/
export const PROJECT_BRIEF_PATHS = [
".github/claude/SYSTEM_PROFILE.md",
"HARE.md",
".hare.md",
"docs/HARE.md",
];

export function contextLoadOrder(path: string): number {
if (/(^|\/)HARE\.md$/i.test(path) || path === ".hare.md") return 0;
if (/schema\.prisma$/i.test(path)) return 1;
return 2;
if (path === ".github/claude/SYSTEM_PROFILE.md") return 0;
if (/(^|\/)HARE\.md$/i.test(path) || path === ".hare.md") return 1;
if (/schema\.prisma$/i.test(path)) return 2;
return 3;
}

export function isProjectBrief(path: string): boolean {
return (
path === ".github/claude/SYSTEM_PROFILE.md" ||
/(^|\/)HARE\.md$/i.test(path) ||
path === ".hare.md"
);
}
6 changes: 4 additions & 2 deletions src/lib/hare/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
runGrokReview,
suggestContextPaths,
} from "./reviewer";
import { contextLoadOrder } from "./briefs";
import { contextLoadOrder, isProjectBrief } from "./briefs";
import { mergeMigrationFindings, scanMigrationIssues } from "./migrations";
import { isStaleRunning } from "./queue";
import type { ChangedFile, ReviewerOutput } from "./types";
Expand Down Expand Up @@ -372,7 +372,9 @@ export async function reviewPullForUser(input: {
for (const path of wanted) {
if (loaded.length >= 8) break;
const content = await getFileAtRef(token, owner, repo, path, headSha);
if (content && content.length > 40) {
if (!content) continue;
const minLen = isProjectBrief(path) ? 20 : 40;
if (content.length > minLen) {
loaded.push({ path, content });
}
}
Expand Down
26 changes: 21 additions & 5 deletions src/lib/hare/reviewer-context.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { PROJECT_BRIEF_PATHS, contextLoadOrder } from "./briefs.ts";
import {
PROJECT_BRIEF_PATHS,
contextLoadOrder,
isProjectBrief,
} from "./briefs.ts";

describe("project brief paths", () => {
it("lists HARE.md and never constitution", () => {
assert.equal(PROJECT_BRIEF_PATHS[0], "HARE.md");
it("prefers SYSTEM_PROFILE.md and never constitution", () => {
assert.equal(PROJECT_BRIEF_PATHS[0], ".github/claude/SYSTEM_PROFILE.md");
assert.ok(!PROJECT_BRIEF_PATHS.some((p) => /constitution/i.test(p)));
});

it("loads HARE.md before schema", () => {
assert.ok(contextLoadOrder("HARE.md") < contextLoadOrder("prisma/schema.prisma"));
it("loads SYSTEM_PROFILE before HARE.md and schema", () => {
assert.ok(
contextLoadOrder(".github/claude/SYSTEM_PROFILE.md") <
contextLoadOrder("HARE.md"),
);
assert.ok(
contextLoadOrder(".github/claude/SYSTEM_PROFILE.md") <
contextLoadOrder("prisma/schema.prisma"),
);
});

it("treats SYSTEM_PROFILE as a brief", () => {
assert.equal(isProjectBrief(".github/claude/SYSTEM_PROFILE.md"), true);
assert.equal(isProjectBrief("src/lib/auth.ts"), false);
});
});
5 changes: 3 additions & 2 deletions src/lib/hare/reviewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ Severity (strict):
- Nit: naming, enums, comments. Sparingly.

Accuracy rules:
- If a HARE.md (or .hare.md) is in repo context, treat it as project law for this review. It beats generic habits. Do not invent rules from CLAUDE workspace profiles or constitution.mdc — those are not this repo.
- If .github/claude/SYSTEM_PROFILE.md is in repo context, treat it as the project brief for this repo. It beats generic habits. If it is missing, review the diff with the normal rules — do not fail the review.
- Do not invent rules from CLAUDE workspace profiles or constitution.mdc (those are not in this git repo).
- Review the diff first. Use repo context files only to verify conventions, not to invent extra scope.
- Cite real paths and NEW-file (right-hand) line numbers from the diff. If you cannot point at a changed line, omit the finding.
- Do not file Critical/Major on a pattern that sibling/context files already use (example: session.user.id is the tenant id everywhere). At most a Nit asking for a comment.
Expand Down Expand Up @@ -190,7 +191,7 @@ export function buildReviewPrompt(input: {
.join("\n\n");

const contextBlock = context
? `\nRepo context. HARE.md (if present) is project law. Other files are conventions only — not part of this diff:\n${context}\n`
? `\nRepo context. .github/claude/SYSTEM_PROFILE.md (if present) is the project brief. Other files are conventions only — not part of this diff. If no brief is present, review the diff with default rules:\n${context}\n`
: "";

return `Repository: ${input.owner}/${input.repo}
Expand Down
Loading