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
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -406,8 +406,6 @@ rulesync.local.jsonc
**/.vibeignore
**/.warpindexingignore
**/.agents/checks/
**/.cursor/BUGBOT.md
**/.hermes/plugins/rulesync-checks/checks/
**/.rovodev/.review-agent.md
!.rulesync/.aiignore
# End of Rulesync
2 changes: 1 addition & 1 deletion docs/reference/file-formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ Amp, Cursor, Hermes Agent, Rovo Dev CLI and Takt consume checks. Amp receives on
- **Project scope:** `.agents/checks/<name>.md`
- **Global scope** (`--global`): `~/.config/amp/checks/<name>.md`

For Cursor, checks are [Bugbot](https://cursor.com/docs/bugbot) code review instructions, and Bugbot reads one aggregated instruction file per directory rather than a file per check — so every check targeting Cursor collapses into the repository-root `.cursor/BUGBOT.md`. Each check becomes one section: an HTML-comment marker carrying the check name, an `## <name>` heading, and the check body as the instruction text (the `description` is used when the body is empty). Bugbot reads the file as free prose, so a check's `severity` and `tools` have no equivalent there — they are not written and do not come back on import, and neither is `description` whenever the check also has a body. Project scope only: Bugbot reads repository files and there is no user-level instruction file. Example output:
For Cursor, checks are [Bugbot](https://cursor.com/docs/bugbot) code review instructions, and Bugbot reads one aggregated instruction file per directory rather than a file per check — so every check targeting Cursor collapses into the repository-root `.cursor/BUGBOT.md`. Each check becomes one section: an HTML-comment marker carrying the check name, an `## <name>` heading, and the check body as the instruction text (the `description` is used when the body is empty). Bugbot reads the file as free prose, so a check's `severity` and `tools` have no equivalent there — they are not written and do not come back on import, and neither is `description` whenever the check also has a body. Project scope only: Bugbot reads repository files and there is no user-level instruction file. Because Bugbot only sees the file when it is **committed**, the derived `.gitignore` deliberately does not ignore `.cursor/BUGBOT.md` (Rovo Dev's `.rovodev/.review-agent.md` gets the same treatment) — commit the generated file for the reviewer to pick it up. Example output:

```md
<!-- rulesync:check:security -->
Expand Down
11 changes: 11 additions & 0 deletions src/cli/commands/gitignore-derive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ const fileToGlob = (relativeDirPath: string | undefined, relativeFilePath: strin
return `**/${toPosix(hasDir ? `${relativeDirPath}/${relativeFilePath}` : relativeFilePath)}`;
};

const isCommittedOutput = (factory: unknown): boolean => {
if (typeof factory !== "object" || factory === null || !("meta" in factory)) return false;
const meta = (factory as { meta?: { committedOutput?: boolean } }).meta;
return meta?.committedOutput === true;
};

const supportsProject = (factory: unknown): boolean => {
if (typeof factory !== "object" || factory === null || !("meta" in factory)) return true;
const meta = (factory as { meta?: { supportsProject?: boolean } }).meta;
Expand Down Expand Up @@ -67,6 +73,10 @@ const deriveDirEntries = (factories: FactoryMap, feature: Feature): GitignoreEnt
for (const [target, factory] of factories) {
if (TARGETS_NOT_DERIVED.has(target)) continue;
if (!supportsProject(factory)) continue;
// Outputs the upstream tool reads from the committed repository (Cursor
// Bugbot's BUGBOT.md, Rovo Dev's .review-agent.md) must not be gitignored:
// ignoring them would disable the very feature the adapter generates.
if (isCommittedOutput(factory)) continue;
const paths = getProjectPaths(factory) as {
relativeDirPath?: string;
relativeFilePath?: string;
Expand All @@ -91,6 +101,7 @@ const deriveFileEntries = (factories: FactoryMap, feature: Feature): GitignoreEn
for (const [target, factory] of factories) {
if (TARGETS_NOT_DERIVED.has(target)) continue;
if (!supportsProject(factory)) continue;
if (isCommittedOutput(factory)) continue;
const paths = getProjectPaths(factory) as {
relativeDirPath?: string;
relativeFilePath?: string;
Expand Down
19 changes: 19 additions & 0 deletions src/cli/commands/gitignore-entries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,3 +471,22 @@ describe("filterGitignoreEntries", () => {
});
});
});

describe("committedOutput check outputs", () => {
it("never derives gitignore entries for outputs upstream reads from the committed repo", async () => {
const derive = await import("./gitignore-derive.js");
const entries = derive.deriveAllGitignoreEntries().map((tag) => tag.entry);
// Bugbot / Rovo Dev's reviewer only see these files when committed;
// ignoring them would disable the checks feature (#2487).
expect(entries).not.toContain("**/.cursor/BUGBOT.md");
expect(entries).not.toContain("**/.rovodev/.review-agent.md");
});

it("keeps the committedOutput flag meaningful (at least one checks factory sets it)", async () => {
const { toolCheckFactories } = await import("../../features/checks/checks-processor.js");
const flagged = [...toolCheckFactories.values()].filter(
(factory) => factory.meta.committedOutput === true,
);
expect(flagged.length).toBeGreaterThan(0);
});
});
22 changes: 20 additions & 2 deletions src/features/checks/checks-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ type ToolCheckFactory = {
supportsGlobal: boolean;
/** File pattern for import (e.g., "*.md") */
filePattern: string;
/**
* Whether the upstream reviewer reads this output from the **committed**
* repository (Cursor Bugbot, Rovo Dev's code reviewer). The gitignore
* derivation skips such outputs — ignoring them would disable the very
* feature the adapter generates. Future checks adapters whose upstream
* reads from the committed tree (e.g. Goose `.agents/checks/`) should set
* this too.
*/
committedOutput?: boolean;
};
};

Expand Down Expand Up @@ -87,7 +96,9 @@ export const toolCheckFactories = new Map<ChecksProcessorToolTarget, ToolCheckFa
// check targeting Cursor collapses into the root `.cursor/BUGBOT.md`.
// https://cursor.com/docs/bugbot
class: CursorCheck,
meta: { supportsGlobal: false, filePattern: CURSOR_BUGBOT_FILE_NAME },
// `committedOutput`: Bugbot only sees BUGBOT.md when it is checked into
// the repository, so the derived .gitignore must not ignore it.
meta: { supportsGlobal: false, filePattern: CURSOR_BUGBOT_FILE_NAME, committedOutput: true },
},
],
[
Expand All @@ -104,7 +115,14 @@ export const toolCheckFactories = new Map<ChecksProcessorToolTarget, ToolCheckFa
// every check targeting it collapses into `.rovodev/.review-agent.md`.
// https://support.atlassian.com/rovo/docs/set-custom-instructions-for-code-reviews/
class: RovodevCheck,
meta: { supportsGlobal: false, filePattern: ROVODEV_REVIEW_AGENT_FILE_NAME },
// `committedOutput`: Rovo Dev's code reviewer reads .review-agent.md
// from the committed repository, so the derived .gitignore must not
// ignore it.
meta: {
supportsGlobal: false,
filePattern: ROVODEV_REVIEW_AGENT_FILE_NAME,
committedOutput: true,
},
},
],
[
Expand Down
2 changes: 1 addition & 1 deletion src/generated/docs-content.ts

Large diffs are not rendered by default.

Loading