Skip to content
Open the dashboard
Developer docs

Agent instruction files

Developers8 min read

groundrule sync writes your standards into the instruction files that coding agents read. This page shows which file each agent gets, what the generated section looks like, which rules go in and in what order, and how your own content in those files is kept.

The targets list in .groundrule/config.yaml decides which agents get files. There are four targets:

Target Files it writes How the file is written
agents-md AGENTS.md A managed block inside the file. Everything else in the file is yours.
claude-code CLAUDE.md A managed block. With agents-md also in targets, the block only imports AGENTS.md. Without it, the block holds the full rules.
cursor .cursor/rules/groundrule.mdc, plus one .cursor/rules/groundrule-<scope>.mdc per group of path-scoped rules Whole files that Groundrule owns.
copilot .github/copilot-instructions.md, plus one .github/instructions/groundrule-<scope>.instructions.md per group of path-scoped rules A managed block in copilot-instructions.md; the per-path files are owned by Groundrule.

All paths are relative to the repository root.

groundrule init picks targets for you:

  • agents-md always, because many agents read AGENTS.md.
  • Every agent it finds signs of in the repository:
    • claude-code for CLAUDE.md or a .claude folder;
    • cursor for .cursor or .cursorrules;
    • copilot for .github/copilot-instructions.md or .github/instructions.
  • With --org, the coding agents your workspace listed during onboarding (Claude Code, Cursor, Copilot).
  • claude-code, when none of the above found anything.

To choose them yourself, pass --targets to init, or edit the list later:

.groundrule/config.yaml
targets:
- agents-md
- claude-code
- cursor
- copilot

Then run npx @groundrule/cli sync again. If you leave targets out of the config entirely, the default is agents-md only. See Configuration.

This is the managed block sync added to an existing AGENTS.md. The two lines above the block were already in the file and stay untouched:

# Checkout API
Run `pnpm test` before pushing.
<!-- groundrule:begin -->
<!-- Generated by Groundrule from `.groundrule/`. Edit the standards there, then run `groundrule sync`. -->
## Engineering standards
These are this repository's engineering ground rules. Follow them in every change.
Before you finish a task, run `npx @groundrule/cli check` and fix what it reports.
For the reasoning and examples behind a rule, run `npx @groundrule/cli explain <ID>`.
### Everywhere
- **SEC-001** No private keys in the repository · _blocker_
Never commit private keys. Load them at runtime from a secret manager or the environment.
- **SEC-002** No environment files in the repository · _blocker_
Do not commit .env files. Commit a .env.example with placeholder values instead.
- Do: `.env.example with placeholder values`
- Don't: `.env with real values`
…
- **TS-009** Import Node.js built-ins with the node: prefix · _info_
Import Node.js built-in modules with the node: prefix (node:fs, node:path, node:crypto, ...) in code that runs on Node.js 16 or later. (typescript, javascript)
- Do: `import { readFile } from "node:fs/promises";`
- Don't: `import { readFile } from "fs/promises";`
### `src/**` (except `src/payments/gateway.ts`)
- **ACME-004** Route all Stripe calls through the gateway · _blocker_
Never import stripe outside src/payments/gateway.ts. Call the gateway's functions, such as createPaymentIntent, instead. (typescript)
- Do: `import { createPaymentIntent } from "../payments/gateway";`
- Don't: `import Stripe from "stripe";` (Direct Stripe import)
<!-- groundrule:end -->

The structure is always the same:

  1. The begin marker, then a comment that says where the rules come from.
  2. An Engineering standards heading and a three-line preamble that tells the agent to follow the rules, to run check before finishing, and to use explain for the reasoning.
  3. One subheading per scope group. Everywhere holds rules with no paths. Each path-scoped group is headed by its paths, with any excluded paths in parentheses.
  4. One entry per standard.
  5. The end marker.

Each entry is short, because agents read every line on every task:

Part Where it comes from
**ID** Title · _severity_ metadata.id, metadata.title and spec.severity.
The sentence below it spec.agent.summary if the standard has one, otherwise spec.requirement. Line breaks are folded into spaces.
(typescript, javascript) The standard’s scope.languages and scope.frameworks, when it has any.
- Do: The first entry in spec.examples.approved.
- Don't: The first entry in spec.examples.forbidden.

Examples render in two ways:

  • A one-line example is written inline: - Do: `const url = process.env.SLACK_WEBHOOK_URL;` . An example’s note follows in parentheses.
  • A multi-line example becomes a fenced code block, indented under the entry, with the example’s language on the fence. The note goes after the label: - Don't (Trusts a client-supplied role.):.

Only the first example of each kind is written. The others stay available through groundrule explain <ID>. To write rules that read well here, see The rule format.

sync includes a standard when all of these are true:

  • It is in effect: its status is active or deprecated, and no config override disables it. Drafts and retired standards are left out.
  • It applies to this repository. Its scope.languages, scope.frameworks, scope.tags and scope.repositories must match what the CLI detects here. A React rule is left out of a repository without React.
  • spec.agent.instruction is not false.
  • With a connected workspace: it is at Teach, Advise or Enforce. Rules at Observe, rules turned off, and drafts never reach agent files. See Rollout stages.

The order is fixed, so the files only change when the rules do:

  1. Rules are grouped by their paths and exclude lists. Rules with the same lists share a group.
  2. Everywhere comes first. Path groups follow, sorted by their paths.
  3. Inside a group, rules are sorted by severity (blocker, warning, advisory, info), then by ID.

The sync headline counts the rules it delivered:

groundrule sync · 36 standards → agents-md, claude-code, cursor, copilot

With a connected workspace, a second line says where they came from, for example From Acme Payments on Groundrule (organization rules): rules at Teach, Advise, and Enforce.

When agents-md is also a target, CLAUDE.md gets a block that imports AGENTS.md, so Claude Code doesn’t read the same rules twice:

<!-- groundrule:begin -->
<!-- Generated by Groundrule from `.groundrule/`. Edit the standards there, then run `groundrule sync`. -->
Engineering standards for this repository are in AGENTS.md:
@AGENTS.md
<!-- groundrule:end -->

When agents-md isn’t a target, the block in CLAUDE.md holds the full Engineering standards section instead.

Cursor gets one rule file per scope group. Groundrule owns these files and rewrites them on every sync.

  • .cursor/rules/groundrule.mdc holds the Everywhere group and applies to every file:

    ---
    description: Engineering standards for this repository
    globs:
    alwaysApply: true
    ---
    <!-- Generated by Groundrule from `.groundrule/`. Edit the standards there, then run `groundrule sync`. -->
    # Engineering standards: Everywhere
  • Each path group gets .cursor/rules/groundrule-<slug>-<hash>.mdc, for example groundrule-src-1w14o.mdc. Its globs are the group’s paths, and it only applies when Cursor works on matching files:

    ---
    description: Engineering standards for src/**
    globs: src/**
    alwaysApply: false
    ---
    <!-- Generated by Groundrule from `.groundrule/`. Edit the standards there, then run `groundrule sync`. -->
    # Engineering standards: `src/**` (except `src/payments/gateway.ts`)

The file name comes from the paths plus a short hash, so it stays the same between runs. Excluded paths appear in the heading but not in globs, because Cursor globs can’t exclude.

Other files in .cursor/rules/ are yours. sync only writes and removes files named groundrule.mdc or groundrule-*.mdc.

  • .github/copilot-instructions.md gets a managed block with the Everywhere rules. Your own instructions in the file stay.

  • Each path group gets .github/instructions/groundrule-<slug>-<hash>.instructions.md, with an applyTo header:

    ---
    applyTo: "src/**"
    ---
    <!-- Generated by Groundrule from `.groundrule/`. Edit the standards there, then run `groundrule sync`. -->
    # Engineering standards: `src/**` (except `src/payments/gateway.ts`)

Other files in .github/instructions/ are yours.

In AGENTS.md, CLAUDE.md and .github/copilot-instructions.md, Groundrule owns only the lines from <!-- groundrule:begin --> to <!-- groundrule:end -->.

The file… What sync does
doesn’t exist, or is empty Creates it with only the managed block.
has content but no markers Adds a blank line and the managed block at the end. Nothing else changes.
has both markers Replaces what is between them. Text above and below stays exactly as it was.

Write your own instructions, such as build commands or architecture notes, above or below the block. Don’t edit inside it: the next sync overwrites those lines. To change a rule, change the standard (in .groundrule/standards/, or in the dashboard for a connected workspace) and sync again.

Owned files (.cursor/rules/groundrule*.mdc and .github/instructions/groundrule-*.instructions.md) are rewritten whole. When a path group disappears, for example because you changed a rule’s paths, sync removes its old file and prints it with − and removed. It only removes owned files of targets that are still in your config.

Run sync whenever the rules change, and commit what it writes:

Terminal window
npx @groundrule/cli sync

Each file is listed with what happened to it:

Mark Meaning
+ created The file didn’t exist.
~ updated The managed block or owned file changed.
− removed An owned file is no longer needed.
= unchanged Nothing to do.

The last line reads ✓ Done. Commit these files so every agent gets the same rules., or ✓ Already up to date. when nothing changed.

Rules change in more than one place:

  • a standard you edited in .groundrule/standards/;
  • a pack update that arrives with a new CLI version;
  • for a connected workspace, a change in the dashboard: a new standard, a rule moved to Teach, an edited wording.

The last one doesn’t touch your repository. Run sync after rulebook changes, or let CI tell you.

sync --check writes nothing. It exits with 0 when every file is current and 1 when any file would change:

✕ Agent instructions are out of date:
.cursor/rules/groundrule.mdc would be created
Run groundrule sync and commit the result.

When everything is current, it prints ✓ Agent instructions are up to date (36 standards).

Add it as a step before check:

- run: npx @groundrule/cli sync --check
env:
GROUNDRULE_TOKEN: ${{ secrets.GROUNDRULE_TOKEN }}

With a connected workspace, sync --check fails as soon as someone changes the rulebook in a way that affects this repository. Someone then runs sync and commits the result. The full CI setup is in Run it in CI.

groundrule doctor runs the same comparison. It reports ✕ Agent instructions are out of date: .cursor/rules/groundrule.mdc, with the hint Run `groundrule sync`., and exits with 1.

If your config extends a pack from GitHub pinned to a tag or commit, sync uses a cached copy. Run sync --refresh to fetch it again. Unpinned GitHub references are fetched on every run. See Environment and files.