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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ versions follow [Semantic Versioning](https://semver.org/).
## [Unreleased]

### Changed
- **`AGENTS.md` investigated and deliberately deferred to M2** ([ADR 0047](docs/decisions/0047-agents-md-deferred-to-m2.md); **nothing in the plugin changes**). The open spec — OpenAI-led, donated to the Linux Foundation, 60k+ repositories — is a single predictable file of project context for AI agents, and this plugin had **no mention of it anywhere** while `docs/ROADMAP.md` M2 states the harness-neutral direction outright. The spec says nothing about coexisting with tool-specific files, so the behaviour was **measured rather than read** (Claude Code 2.1.280, three throwaway directories, a fresh `claude -p` with read tools disabled so the answer comes from what was *loaded*): `AGENTS.md` alone is read; `AGENTS.md` **beside** a `CLAUDE.md` is **ignored entirely**; a `CLAUDE.md` containing `@AGENTS.md` loads both. **That kills the cheapest-looking option** — a pointer would be read by no Claude Code session while looking installed, the failure class logged twice this week. Two further measurements are recorded so M2 does not re-derive them: the generated `CLAUDE.md` is **~86% harness-neutral**, its Claude-bound lines clustering in three identifiable blocks rather than diffusing; and `CLAUDE.md` is referenced **77 times across 10 skills** plus 10 templates, which is what makes a rename disproportionate today. The recommendation carried forward is the **`@AGENTS.md` import**, not a pointer and not a rename. One argument found along the way **does not need M2**: the methodology's entry points are written *as commands* (*"decided → `/groundrules:add-adr`"*), meaningless to another tool or to a human without the plugin — splitting would force the portable half to state the **act and the place**, which is a better generated file regardless.
- **The ADR index invariant becomes a check** (`test/check-adr-index.sh`, **dogfood only**). On 2026-09-21 an ADR landed on `main` without its row in `docs/decisions/README.md` and survived, because the only thing guarding that invariant was a line in the release checklist — read at release, days after the omission. A set comparison has exactly one correct answer, so it is a check, not a reminder, and not an eval case: an eval measures whether configuration changes behaviour, which is the wrong instrument for *is every file listed*. The script compares both directions — a file with no row, a row with no file — and flags duplicates, since a row listed twice reads as agreement until someone diffs them. Deterministic, offline, zero tokens; verified to fail as well as pass. **It runs on every pull request** (`.github/workflows/adr-index.yml` — one workflow, one job, one step; the stack is pure Markdown and JSON, so CI is a real surface and stays the size of the invariant it guards). The first placement considered was a git `pre-commit` hook, rejected on a mix of one sound reason and one that turned out false — **corrected the same day, see below**. `core.hooksPath` on the maintainer's machine does point at `ggshield`'s directory, and that setting *replaces* the hooks directory rather than adding to it, so **git itself** never looks in `.git/hooks`. But the occupying script **calls the repository's hook and fails the commit if it fails**, so a local hook would have run after all. CI is the better instrument rather than the fallback: it travels with the clone, `--no-verify` cannot walk past it, and it fires at the **pull request**, which is where the omission landed. **Proven both ways** before shipping — a throwaway PR removing an index row went red naming the file, restoring the row turned the same workflow green. A workflow whose red has never been seen is a badge. The lesson generalises in `docs/LEARNINGS.md`, sharpened by the correction: **read what occupies the slot — occupancy is not a second question, it answers the first.** Whether your control runs is a property of the occupant's *code*, not of the configuration pointing at it; here the configuration said *unreachable* and the script said *I call it myself*. Skipping that read cuts the other way too: writing into the `ggshield` directory itself, where the entries are symlinks into `dotfiles` beside a `.bak-` from a previous overwrite, **would** have destroyed a working guard while reading as installed.
- **A false claim was recorded and corrected the same day, and the failure is logged** (`docs/AGENT-EVALS.md`, 2026-09-21). The *repository hooks are dead here* half came from a peer, asserted from one measurement and generalised past it. It was not accepted blindly — the redirect, the directory listing and `git rev-parse --git-path hooks` were all checked, and all three were true. **The conclusion drawn from them was not, and the file it was about was never opened.** That is a variant of *asserts without verifying* that survives the usual guard, because verification happened on the **premises**: checking every fact a peer measured and adopting their inference is agreement with extra steps, and it feels like diligence. The tell is that the conclusion concerns something none of the checked commands touched. **The decision did not move**: CI still wins for the two reasons that were never about the slot — `.git/hooks/` is not versioned and does not survive a clone, and `--no-verify` walks past any local hook including for whoever merges the PR that introduces the defect.
- **[ADR 0045](docs/decisions/0045-the-lock-was-scoped-to-four-skills.md) states both of its conditions where they are read.** Its heading announced *additive, reversible, and under version control*, while the condition that actually made the cut — *does being locked block a methodology from starting* — appeared only in the exclusion sentence below. A reader applying the announced criterion to `close` reaches the opposite conclusion from the one the ADR reached. Both conditions are now stated together, with `close` kept as the worked example: it is the safest skill in the plugin, proposing and never writing without confirmation, so on safety alone it would be the strongest candidate of the fifteen — and it stays locked because a locked `close` blocks nothing from starting. That is what shows the second condition is doing work rather than decorating the first.
Expand Down
7 changes: 7 additions & 0 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ The strategic reason the repo is named `groundrules` (harness-neutral). Keep the
which harnesses first, how the Markdown SKILL instructions port vs. need per-harness adapters, and
the per-harness distribution model (there is no universal "plugin").

> **Start here**: [ADR 0047](decisions/0047-agents-md-deferred-to-m2.md) parks the `AGENTS.md`
> question against this milestone with the groundwork already done — the spec read, the harness
> behaviour measured (a pointer `AGENTS.md` beside a `CLAUDE.md` is read by **nothing**; an
> `@AGENTS.md` import loads both), the generated file measured at ~86% harness-neutral, and the cost
> counted at 77 references across 10 skills. Re-run its three-directory test before acting: those
> numbers are a product behaviour, not a contract.

### M3 — Cross-project dashboard (companion tool)

A tool that scans a folder of projects, detects the groundrules ones (normalized structure +
Expand Down
121 changes: 121 additions & 0 deletions docs/decisions/0047-agents-md-deferred-to-m2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<!-- generated-by: groundrules v1.12.0 -->
# 0047 — `AGENTS.md`: deferred to M2, with the measurements taken now

**Date**: 2026-09-23
**Status**: Accepted (the decision is to defer; the trigger is named below)

## Context

[`AGENTS.md`](https://agents.md) is an open specification for a single, predictable file carrying
project context for AI coding agents — *a README for agents*. Formalised in August 2025 under
OpenAI's lead with Google, Cursor and Factory, donated to the Linux Foundation's Agentic AI
Foundation in December 2025, and reported at 60,000+ repositories and 20+ supporting tools. Plain
Markdown, **no required schema**: the spec says *"use any headings you like; the agent simply parses
the text you provide."* In a monorepo the nearest file in the tree wins.

This plugin generates `CLAUDE.md` and has **no mention of `AGENTS.md` anywhere**, while
[M2](../ROADMAP.md) states the harness-neutral direction outright: *"keep the generated output
harness-agnostic; only the delivery of the skills changes per harness."* That is a gap between a
recorded direction and what the plugin ships, and it surfaced from a third-party guide that merely
named the file without arguing for it.

**The spec does not settle the question**: it says nothing about coexisting with tool-specific files
like `CLAUDE.md`. Only the harness does, so the behaviour was measured rather than read.

### Measured on 2026-09-23, Claude Code 2.1.280

Three throwaway directories, each with distinctive markers, queried by a fresh `claude -p` with all
read tools disabled — so the answer comes from what was *loaded*, not from what it could open:

| Files present | Loaded |
|---|---|
| `AGENTS.md` alone | `AGENTS.md` |
| `AGENTS.md` **and** `CLAUDE.md` | **`CLAUDE.md` only** — the other is ignored |
| `CLAUDE.md` containing `@AGENTS.md` | **both** |

**This kills the cheapest-looking option.** An `AGENTS.md` pointing at `CLAUDE.md`, sitting beside
it, is **read by no Claude Code session**. It would serve other tools only, while looking installed
to anyone reading the repository — the failure class this repository has logged twice this week.

### The other two measurements

**Content**: of the 199 lines of `CLAUDE.md.tpl`, **~86% owe nothing to Claude Code**. The
harness-bound lines are 9 citing a `/groundrules:*` command and 18 citing the interface or the
`.claude/` tree, and they **cluster** — `### Permissions and settings`, `## Claude Code workflow`,
and the `.claude/` subtree under `## Key files and folders`. It is not a diffusion.

**Cost**: `CLAUDE.md` is referenced **77 times across 10 skills** (`bootstrap` 20, `adopt` 17,
`slim` 11, `migrate` 10…) and in 10 templates. A rename is invasive and would need a migration for
every existing project. The import pattern is additive, but not free: `slim` measures a 200-line
budget that would then span two files, and `verify-bootstrap` would need to know about the second.

## Decision

**Defer. Nothing changes in the plugin now.** No `AGENTS.md` is generated, no template splits, no
skill learns a second filename.

**The trigger is [M2](../ROADMAP.md) — *support harnesses beyond Claude Code*.** M2's own ADR is the
place this belongs, because the question is only worth answering alongside *which harnesses first*
and *how the skills are delivered per harness*. Answering it alone would commit the output format
before the delivery model it is meant to serve exists.

**What the measurements buy, and why they are recorded rather than re-derived**: they expire slowly
(a spec and a file layout), while the effort to obtain them is a guide-agent round trip plus three
`claude -p` runs. Re-deriving them at M2 would cost the same again and risk a different answer from
a different method.

**The recommendation to carry into M2, should it be taken**: `CLAUDE.md` **imports** `@AGENTS.md`,
which holds the portable content; `CLAUDE.md` keeps the import line and the three harness-bound
blocks. Not a pointer — measured dead. Not a rename — cost without a matching benefit today.

**One argument found along the way that stands on its own**, independent of any harness: the
methodology's entry points are currently written *as commands* (*"decided → `/groundrules:add-adr`"*),
which is meaningless to any other tool and to a human reading the repository without the plugin
installed. Splitting the file forces the portable half to state **the act and the place** — *record
the decision as an ADR in `docs/decisions/`* — with the command as a shorthand beside it. That is a
better generated file whatever happens to `AGENTS.md`, and it would probably never be written
without this constraint. **It does not need M2**; if M2 is never tackled, it is still worth doing.

## Alternatives considered

- **Generate a pointer `AGENTS.md` now** — rejected on measurement, not on taste: with a `CLAUDE.md`
present it is read by nothing, and a file that looks installed and is inert is worse than its
absence.
- **Rename the generated file to `AGENTS.md`** — rejected for now: 77 references across 10 skills,
10 templates, and a migration for every bootstrapped project, to serve a portability nobody has
yet asked for. Reconsider at M2, where the delivery model makes it a real question.
- **Adopt the import pattern now** — the option this ADR recommends *for later*, rejected *now* for
lack of demand. Its cost is small but not zero, and `slim`'s budget across two files is a design
question that deserves M2's context rather than an afternoon's.
- **Say nothing and revisit from scratch at M2** — rejected. The three measurements are the
expensive part and they do not spoil; discarding them is how the same afternoon gets spent twice.

## Consequences

### Positive
- The gap between M2's stated direction and what ships is now **recorded rather than latent**.
- M2 starts with the harness behaviour already measured and the cost already counted.
- The command-versus-act wording problem is named, and can be fixed without waiting for M2.

### Negative / Tradeoffs
- **A generated project stays Claude-Code-shaped** for anyone using another tool on the same
repository. That is the status quo, now with a reason attached instead of an omission.
- Deferred decisions rot: the spec may move, and the precedence rule measured here is a *product
behaviour*, not a contract. **Re-run the three-directory test before acting on this ADR** — the
numbers in it are a snapshot, not a guarantee.

### Neutral
- No change to any skill, template, or generated file.

## Notes

- Spec: <https://agents.md>. Surfaced by a third-party guide that named the file without arguing it;
everything load-bearing here came from reading the spec and measuring the harness.
- **One claim in the investigation was not verifiable from this machine** and is therefore not
load-bearing: that `AGENTS.md` support depends on server-side feature flags and is absent on
Bedrock, third-party providers and telemetry-disabled sessions. It came from documentation via a
subagent. It would argue *against* making `AGENTS.md` the sole entry point — which the recommended
import pattern does not do, so the recommendation holds either way. Verify it at M2 if it matters.
- Related: [ADR 0023](0023-project-scope-for-team-portability.md) (portability of the output),
[ADR 0021](0021-context-economy-index-over-doc-search.md) (why duplicated context is a cost),
[`docs/ROADMAP.md`](../ROADMAP.md) M2.
1 change: 1 addition & 0 deletions docs/decisions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,4 @@ No ADR needed for trivial choices or implementation details.
| [0044](0044-runner-enforces-verifier-isolation.md) | The runner spawns the verifier separately; the loop's isolation becomes a fact rather than a request | Accepted | 2026-09-09 |
| [0045](0045-the-lock-was-scoped-to-four-skills.md) | `disable-model-invocation` applied per skill: five methodology skills open, five destructive ones stay locked (refines 0003) | Accepted | 2026-09-21 |
| [0046](0046-orchestrated-working-regime.md) | Orchestrated regime: the request for work carries the authorisation for the commit (amends 0036's commit rule) | Accepted | 2026-09-21 |
| [0047](0047-agents-md-deferred-to-m2.md) | `AGENTS.md` deferred to M2, with the harness behaviour measured now (a pointer is read by nothing) | Accepted | 2026-09-23 |
Loading