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
3 changes: 2 additions & 1 deletion src/cli/commands/gitignore-entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export const GITIGNORE_ENTRY_REGISTRY: ReadonlyArray<GitignoreEntryTag> = [
},
{ target: "common", feature: "general", entry: ".rulesync/rules/*.local.md" },
{ target: "common", feature: "general", entry: "rulesync.local.jsonc" },
{ target: "common", feature: "general", entry: "!.rulesync/.aiignore" },
// AGENTS.local.md is placed in common scope (not rovodev-only) so that
// local rule files are always gitignored regardless of which targets are enabled.
// This prevents accidental commits when a user disables the rovodev target.
Expand Down Expand Up @@ -178,6 +177,8 @@ export const GITIGNORE_ENTRY_REGISTRY: ReadonlyArray<GitignoreEntryTag> = [
{ target: "kiro", feature: "subagents", entry: "**/.kiro/agents/" },
{ target: "kiro", feature: "mcp", entry: "**/.kiro/settings/mcp.json" },
{ target: "kiro", feature: "ignore", entry: "**/.aiignore" },
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

**/.aiignore is currently tagged only for the kiro target, but Junie also generates a root .aiignore file (see JunieIgnore.getSettablePaths() and e2e ignore coverage). With rulesync gitignore --targets junie, this entry won’t be included, so the generated .aiignore won’t be gitignored. Consider changing this registry tag to apply to both kiro and junie (e.g., target: ["kiro","junie"]) so target-filtered output stays consistent.

Suggested change
{ target: "kiro", feature: "ignore", entry: "**/.aiignore" },
{ target: ["kiro", "junie"], feature: "ignore", entry: "**/.aiignore" },

Copilot uses AI. Check for mistakes.
// Keep this after ignore entries like "**/.aiignore" so the exception remains effective.
{ target: "common", feature: "general", entry: "!.rulesync/.aiignore" },

// OpenCode
{ target: "opencode", feature: "commands", entry: "**/.opencode/command/" },
Expand Down
12 changes: 12 additions & 0 deletions src/cli/commands/gitignore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,18 @@ describe("gitignoreCommand", () => {
expect(content).not.toContain("**/.agent/skills/");
});

it("should place .rulesync/.aiignore exception after .aiignore ignore entries", async () => {
vi.mocked(fileExists).mockResolvedValue(false);

await gitignoreCommand(mockLogger);

const writeCall = vi.mocked(writeFileContent).mock.calls[0];
expect(writeCall).toBeDefined();
const content = writeCall![1];

Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new ordering assertion relies on indexOf and will pass if **/.aiignore isn't present but the earlier test that checks toContain("**/.aiignore") is removed/changed later. Consider asserting content contains both patterns (or that both indexOf values are >= 0) before comparing indexes so failures are clearer and the test can’t become a false-positive over time.

Suggested change
expect(content).toContain("**/.aiignore");
expect(content).toContain("!.rulesync/.aiignore");

Copilot uses AI. Check for mistakes.
expect(content.indexOf("**/.aiignore")).toBeLessThan(content.indexOf("!.rulesync/.aiignore"));
});

it("should format content properly with newline at end", async () => {
vi.mocked(fileExists).mockResolvedValue(false);

Expand Down
Loading