Skip to content

Commit 00a024e

Browse files
authored
Merge pull request #2553 from dyoshikawa/resolve-scrap-issue-2487-committed-check-outputs
feat(gitignore): do not ignore check outputs the upstream reads from the committed repo
2 parents 43f3a85 + 62efcdc commit 00a024e

6 files changed

Lines changed: 52 additions & 6 deletions

File tree

.gitignore

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,8 +406,6 @@ rulesync.local.jsonc
406406
**/.vibeignore
407407
**/.warpindexingignore
408408
**/.agents/checks/
409-
**/.cursor/BUGBOT.md
410409
**/.hermes/plugins/rulesync-checks/checks/
411-
**/.rovodev/.review-agent.md
412410
!.rulesync/.aiignore
413411
# End of Rulesync

docs/reference/file-formats.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ Amp, Cursor, Hermes Agent, Rovo Dev CLI and Takt consume checks. Amp receives on
527527
- **Project scope:** `.agents/checks/<name>.md`
528528
- **Global scope** (`--global`): `~/.config/amp/checks/<name>.md`
529529

530-
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:
530+
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:
531531

532532
```md
533533
<!-- rulesync:check:security -->

src/cli/commands/gitignore-derive.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ const fileToGlob = (relativeDirPath: string | undefined, relativeFilePath: strin
4040
return `**/${toPosix(hasDir ? `${relativeDirPath}/${relativeFilePath}` : relativeFilePath)}`;
4141
};
4242

43+
const isCommittedOutput = (factory: unknown): boolean => {
44+
if (typeof factory !== "object" || factory === null || !("meta" in factory)) return false;
45+
const meta = (factory as { meta?: { committedOutput?: boolean } }).meta;
46+
return meta?.committedOutput === true;
47+
};
48+
4349
const supportsProject = (factory: unknown): boolean => {
4450
if (typeof factory !== "object" || factory === null || !("meta" in factory)) return true;
4551
const meta = (factory as { meta?: { supportsProject?: boolean } }).meta;
@@ -67,6 +73,10 @@ const deriveDirEntries = (factories: FactoryMap, feature: Feature): GitignoreEnt
6773
for (const [target, factory] of factories) {
6874
if (TARGETS_NOT_DERIVED.has(target)) continue;
6975
if (!supportsProject(factory)) continue;
76+
// Outputs the upstream tool reads from the committed repository (Cursor
77+
// Bugbot's BUGBOT.md, Rovo Dev's .review-agent.md) must not be gitignored:
78+
// ignoring them would disable the very feature the adapter generates.
79+
if (isCommittedOutput(factory)) continue;
7080
const paths = getProjectPaths(factory) as {
7181
relativeDirPath?: string;
7282
relativeFilePath?: string;
@@ -91,6 +101,7 @@ const deriveFileEntries = (factories: FactoryMap, feature: Feature): GitignoreEn
91101
for (const [target, factory] of factories) {
92102
if (TARGETS_NOT_DERIVED.has(target)) continue;
93103
if (!supportsProject(factory)) continue;
104+
if (isCommittedOutput(factory)) continue;
94105
const paths = getProjectPaths(factory) as {
95106
relativeDirPath?: string;
96107
relativeFilePath?: string;

src/cli/commands/gitignore-entries.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,3 +471,22 @@ describe("filterGitignoreEntries", () => {
471471
});
472472
});
473473
});
474+
475+
describe("committedOutput check outputs", () => {
476+
it("never derives gitignore entries for outputs upstream reads from the committed repo", async () => {
477+
const derive = await import("./gitignore-derive.js");
478+
const entries = derive.deriveAllGitignoreEntries().map((tag) => tag.entry);
479+
// Bugbot / Rovo Dev's reviewer only see these files when committed;
480+
// ignoring them would disable the checks feature (#2487).
481+
expect(entries).not.toContain("**/.cursor/BUGBOT.md");
482+
expect(entries).not.toContain("**/.rovodev/.review-agent.md");
483+
});
484+
485+
it("keeps the committedOutput flag meaningful (at least one checks factory sets it)", async () => {
486+
const { toolCheckFactories } = await import("../../features/checks/checks-processor.js");
487+
const flagged = [...toolCheckFactories.values()].filter(
488+
(factory) => factory.meta.committedOutput === true,
489+
);
490+
expect(flagged.length).toBeGreaterThan(0);
491+
});
492+
});

src/features/checks/checks-processor.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ type ToolCheckFactory = {
5353
supportsGlobal: boolean;
5454
/** File pattern for import (e.g., "*.md") */
5555
filePattern: string;
56+
/**
57+
* Whether the upstream reviewer reads this output from the **committed**
58+
* repository (Cursor Bugbot, Rovo Dev's code reviewer). The gitignore
59+
* derivation skips such outputs — ignoring them would disable the very
60+
* feature the adapter generates. Future checks adapters whose upstream
61+
* reads from the committed tree (e.g. Goose `.agents/checks/`) should set
62+
* this too.
63+
*/
64+
committedOutput?: boolean;
5665
};
5766
};
5867

@@ -87,7 +96,9 @@ export const toolCheckFactories = new Map<ChecksProcessorToolTarget, ToolCheckFa
8796
// check targeting Cursor collapses into the root `.cursor/BUGBOT.md`.
8897
// https://cursor.com/docs/bugbot
8998
class: CursorCheck,
90-
meta: { supportsGlobal: false, filePattern: CURSOR_BUGBOT_FILE_NAME },
99+
// `committedOutput`: Bugbot only sees BUGBOT.md when it is checked into
100+
// the repository, so the derived .gitignore must not ignore it.
101+
meta: { supportsGlobal: false, filePattern: CURSOR_BUGBOT_FILE_NAME, committedOutput: true },
91102
},
92103
],
93104
[
@@ -104,7 +115,14 @@ export const toolCheckFactories = new Map<ChecksProcessorToolTarget, ToolCheckFa
104115
// every check targeting it collapses into `.rovodev/.review-agent.md`.
105116
// https://support.atlassian.com/rovo/docs/set-custom-instructions-for-code-reviews/
106117
class: RovodevCheck,
107-
meta: { supportsGlobal: false, filePattern: ROVODEV_REVIEW_AGENT_FILE_NAME },
118+
// `committedOutput`: Rovo Dev's code reviewer reads .review-agent.md
119+
// from the committed repository, so the derived .gitignore must not
120+
// ignore it.
121+
meta: {
122+
supportsGlobal: false,
123+
filePattern: ROVODEV_REVIEW_AGENT_FILE_NAME,
124+
committedOutput: true,
125+
},
108126
},
109127
],
110128
[

src/generated/docs-content.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)