Skip to content

fix(rules): deliver a folded rule once to providers that also read AGENTS.md - #261

Merged
Minitour merged 2 commits into
developfrom
fix/duplicate-rule-delivery
Sep 18, 2026
Merged

Minitour merged 2 commits into
developfrom
fix/duplicate-rule-delivery

Conversation

@Minitour

Copy link
Copy Markdown
Member

Summary

Cursor reads AGENTS.md, so a rule sent to both Codex and Cursor reached Cursor twice: once from .cursor/rules/<id>.mdc and once from the block capa folds into AGENTS.md for Codex. This happened for any shared rule, not just scoped ones. Closes #260.

What changed

  • planRulePlacement now returns nativeCovered: for each provider with its own rules dir, the rules it already gets because it reads every file the rule is folded into. The folded copy is always at least as broad as the native one, so the native file only added a duplicate.
  • installRules skips those native files, and pruneRules removes a stale one (for example when Codex is added to a Cursor-only project).
  • New scope-widened warning: if the fold is a root > Applies to: fallback (for example **/*.py), the Cursor-side reader loses its native appliesTo scope. This warns even under scope: best-effort, since that opt-in was made for the providers doing the folding. It's always a warning and never skips the rule, so Codex's only copy is never dropped.
  • Updated the capabilities-manager schema reference.

Not covered: per-provider instruction filenames and native Codex scoped rules both depend on upstream support (openai/codex#34002, Cursor AGENTS.md opt-out).

Screenshots / logs

New warning:

Rule "py": cursor also reads AGENTS.md, so it gets the project-wide copy folded for codex instead of its native appliesTo scope (its own rule file is skipped to avoid a duplicate). Use directory globs (e.g. "src/**") to keep the scope for every provider.

Test plan

  • New tests in rules-shared-instructions.test.ts: unscoped and services/** rules land once for codex+cursor; **/*.py best-effort warns scope-widened; a cursor-only rule keeps its .mdc; prune removes a now-duplicate .mdc
  • Adjusted the error-mode test setup, which relied on the duplicate
  • bun test src/cli src/shared has no new failures vs develop on Windows (the existing EBUSY/symlink failures are unchanged)
  • bunx tsc --noEmit passes

Checklist

  • Tests added or updated
  • bunx tsc --noEmit passes
  • Docs updated (if user-facing): skill reference updated. The public docs page (capa-docs/resources/rules.mdx) lives in content-management-infra and needs a follow-up there

🤖 Generated with Claude Code

…ENTS.md

Cursor reads AGENTS.md, so a rule targeted at both Codex and Cursor reached
Cursor twice: natively in .cursor/rules and via the block folded for Codex.
The placement plan now records which native-rules providers already read
every file a rule is folded into; install skips their native file and prune
removes a stale one. When the fold is a root "Applies to" fallback, a
scope-widened warning is emitted even under scope: best-effort.

Closes #260

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Prevent duplicate folded rule delivery to native-rule providers

🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Prevent duplicate rules when native-rule providers also read folded instruction files.
• Prune stale native copies and warn when folded delivery widens native scope.
• Document and test shared, scoped, provider-specific, and migration scenarios.
Diagram

graph TD
  R["Rule definitions"] --> P["Placement planner"] --> D{"Fold covers native?"}
  D -->|Yes| C["Native coverage"] --> I["Rule installer"] --> S["Shared instructions"]
  C --> Q["Rule pruner"]
  D -->|No| I
  I --> N["Native rule files"]
Loading
High-Level Assessment

The PR’s centralized placement-plan approach is appropriate because installation and pruning consume the same native-coverage decision, preserving order-independent behavior. Keeping both copies would retain the bug, while isolating Cursor from folded AGENTS.md content depends on unsupported upstream provider controls.

Files changed (4) +107 / -7

Bug fix (2) +50 / -4
rules-installer.tsSkip and prune native rules covered by folded instructions +4/-2

Skip and prune native rules covered by folded instructions

• Uses placement-plan native coverage to omit redundant provider rule files during installation. Applies the same coverage map during pruning so previously managed duplicates are removed.

src/cli/utils/rules-installer.ts

rules-placement.tsTrack native providers covered by folded rule placements +46/-2

Track native providers covered by folded rule placements

• Extends placement plans with provider-to-rule native coverage derived from shared instruction readers. Adds 'scope-widened' warnings when deduplication replaces native scoping with a project-wide folded fallback.

src/cli/utils/rules-placement.ts

Tests (1) +56 / -3
rules-shared-instructions.test.tsCover shared instruction deduplication and migration behavior +56/-3

Cover shared instruction deduplication and migration behavior

• Adds tests for unscoped and directory-scoped deduplication, best-effort scope widening, provider-restricted rules, and stale native-file pruning. Adjusts the error-conflict setup to create the prior Cursor file from a Cursor-only installation.

src/cli/utils/tests/rules-shared-instructions.test.ts

Documentation (1) +1 / -0
capabilities-schema.mdDocument folded-rule deduplication and scope widening +1/-0

Document folded-rule deduplication and scope widening

• Explains that native rule files are skipped when the provider already reads every folded destination. Documents the 'scope-widened' warning and recommends directory globs for preserving scope.

skills/capabilities-manager/references/capabilities-schema.md

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 18, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Scoped rules disappear from Cursor ✓ Resolved 📎 Requirement gap ≡ Correctness
Description
planRulePlacement marks rules as nativeCovered from planned nested placements before
installRules verifies that the placement directories exist, causing both installation and pruning
to exclude Cursor's native artifact. When a directory-scoped target such as src/** or
services/** is absent, the folded write is skipped with only a warning while the normal
prune-first flow can remove the prior .mdc, so neither Cursor nor Codex retains a copy.
Code

src/cli/utils/rules-installer.ts[R280-282]

+    const covered = plan.nativeCovered.get(provider.id);
  const applicableRules = rules.filter((r) => {
-      if (skipped.has(r.id)) return false;
+      if (skipped.has(r.id) || covered?.has(r.id)) return false;
Evidence
Directory globs create nested placements without checking filesystem existence, and the resulting
coverage state immediately makes the native artifact ineligible for installation and classifies an
existing native file as undesired during pruning. Because the normal command prunes first and
installation later skips folded targets whose parent directories are absent, the provider transition
reproducibly violates Rule 5's requirement for one effective, scope-faithful copy per intended
provider.

Prefer One Faithful Delivery Mechanism Per Provider
src/cli/utils/rules-placement.ts[255-261]
src/cli/utils/rules-installer.ts[280-282]
src/cli/utils/rules-installer.ts[354-359]
src/cli/utils/rules-installer.ts[463-465]
src/cli/utils/rules-placement.ts[312-349]
src/cli/utils/rules-placement.ts[246-261]
src/cli/utils/rules-installer.ts[280-283]
src/cli/utils/rules-installer.ts[329-348]
src/cli/utils/rules-installer.ts[460-493]
src/cli/commands/install-tasks/index.ts[43-49]
src/cli/commands/install-tasks/install-rules.ts[142-149]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Native rule delivery is suppressed and an existing native artifact can be pruned based on a planned nested folded placement before confirming that the replacement is deliverable. If the scoped target directory is missing or rejected, the folded write is skipped, leaving neither the folded copy nor Cursor's native copy.
## Fix Focus Areas
- src/cli/utils/rules-placement.ts[246-261]
- src/cli/utils/rules-installer.ts[280-283]
- src/cli/utils/rules-installer.ts[329-348]
- src/cli/utils/rules-installer.ts[354-359]
- src/cli/utils/rules-installer.ts[460-493]
- src/cli/utils/__tests__/rules-shared-instructions.test.ts[212-224]
## Recommended Fix
Only treat a native rule as covered, suppress it during installation, or prune its existing artifact when the folded replacement is actually deliverable. Apply the same target-viability check during installation and pruning, preserve the native rule when nested targets are missing or rejected, and add a Codex-plus-Cursor transition test where Cursor's existing native rule remains after Codex is added while the directory referenced by the rule's glob is absent.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Cursor still receives shared rules twice ✓ Resolved 🐞 Bug ≡ Correctness
Description
planRulePlacement uses placements.every(...), so an allowed native provider is considered
covered only if it reads every file emitted for every folding provider. With Codex, Gemini CLI, and
Cursor enabled for one unrestricted rule, Cursor reads the AGENTS.md copy but not Gemini's
GEMINI.md copy, leaving its .cursor/rules file installed and delivering the rule twice.
Code

src/cli/utils/rules-placement.ts[R255-258]

+        const readsAll = placements.every((p) =>
+          (layout.files.get(posix.basename(p.path)) ?? []).includes(pid),
+        );
+        if (!readsAll) continue;
Evidence
The planner creates placements for each folding target's selected filename, while Cursor reads
AGENTS.md and Gemini is isolated to GEMINI.md. Requiring Cursor to read both filenames leaves native
coverage unset, after which the installer writes both the AGENTS.md block and Cursor's native file.

src/cli/utils/rules-placement.ts[169-174]
src/cli/utils/rules-placement.ts[218-229]
src/cli/utils/rules-placement.ts[246-261]
src/shared/providers/entries/cursor.ts[18-26]
src/shared/providers/entries/gemini-cli.ts[23-34]
src/cli/utils/rules-installer.ts[280-287]
src/cli/utils/rules-installer.ts[329-366]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Native coverage requires a provider to read every folded placement, so isolated copies generated for other providers prevent duplicate suppression even when the provider receives a complete folded copy through its own instruction channel.
## Fix Focus Areas
- src/cli/utils/rules-placement.ts[246-277]
- src/cli/utils/__tests__/rules-placement.test.ts[62-70]
- src/cli/utils/__tests__/rules-shared-instructions.test.ts[174-202]
## Recommended Fix
Determine whether each native provider reads a complete set of rule locations through at least one folded instruction filename, rather than requiring it to read isolated copies generated for other providers. Add a Codex, Gemini CLI, and Cursor test proving Cursor receives only the AGENTS.md copy and no native rule file.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can type 'qodo, fix this' on a finding and the fix lands right on your PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/cli/utils/rules-installer.ts
Comment thread src/cli/utils/rules-placement.ts Outdated
…lly gets

Address review on #261:
- Coverage now checks only the provider's own instructions file, so an
  isolated GEMINI.md copy no longer blocks deduping Cursor.
- A nested folded placement only counts once its directory exists and is
  capa-owned (same checks install uses), so prune never deletes a Cursor
  rule whose replacement won't be written.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Minitour
Minitour merged commit 2e29184 into develop Sep 18, 2026
8 checks passed
@Minitour
Minitour deleted the fix/duplicate-rule-delivery branch September 18, 2026 14:33
@Minitour Minitour linked an issue Sep 22, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Detect duplicate rule delivery through shared AGENTS.md

1 participant