From 252ec6752b6f66f4cd19a9cac75ad63799f7fa75 Mon Sep 17 00:00:00 2001 From: ginxx009 <28310693+ginxx009@users.noreply.github.com> Date: Wed, 16 Sep 2026 04:48:25 +0000 Subject: [PATCH] Read .github/claude/SYSTEM_PROFILE.md as the project brief. If the file is missing, the review still runs on the diff. No fail, no constitution, no CLAUDE workspace profiles. --- README.md | 8 +++++--- docs/HARE.example.md | 27 ++++----------------------- src/lib/hare/briefs.ts | 27 ++++++++++++++++++++++----- src/lib/hare/engine.ts | 6 ++++-- src/lib/hare/reviewer-context.test.ts | 26 +++++++++++++++++++++----- src/lib/hare/reviewer.ts | 5 +++-- 6 files changed, 59 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 8940a7a..f7068df 100644 --- a/README.md +++ b/README.md @@ -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`. --- diff --git a/docs/HARE.example.md b/docs/HARE.example.md index 021b8b2..e67177d 100644 --- a/docs/HARE.example.md +++ b/docs/HARE.example.md @@ -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 — - -## 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. diff --git a/src/lib/hare/briefs.ts b/src/lib/hare/briefs.ts index 263a437..4711762 100644 --- a/src/lib/hare/briefs.ts +++ b/src/lib/hare/briefs.ts @@ -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" + ); } diff --git a/src/lib/hare/engine.ts b/src/lib/hare/engine.ts index 9185429..2817d29 100644 --- a/src/lib/hare/engine.ts +++ b/src/lib/hare/engine.ts @@ -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"; @@ -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 }); } } diff --git a/src/lib/hare/reviewer-context.test.ts b/src/lib/hare/reviewer-context.test.ts index 2d42cae..9b16cc4 100644 --- a/src/lib/hare/reviewer-context.test.ts +++ b/src/lib/hare/reviewer-context.test.ts @@ -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); }); }); diff --git a/src/lib/hare/reviewer.ts b/src/lib/hare/reviewer.ts index 497a73d..46b2726 100644 --- a/src/lib/hare/reviewer.ts +++ b/src/lib/hare/reviewer.ts @@ -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. @@ -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}