diff --git a/docs/guides/authoring-hubs.md b/docs/guides/authoring-hubs.md index ea780eb..3887cd4 100644 --- a/docs/guides/authoring-hubs.md +++ b/docs/guides/authoring-hubs.md @@ -127,11 +127,21 @@ surf verify --follow # renamed symbol OR moved file: re-point the anc Verifying without reading is the failure mode the whole tool exists to prevent. A green gate promises only "nothing anchored changed since last sign-off" — never that the prose is true. -## Hubs and `AGENTS.md` +## Where claims can live -Hubs are *declarative* domain briefings; `AGENTS.md` is *imperative* operating instructions for -coding agents. Keep them separate — don't copy hub prose into `AGENTS.md`. Instead, give -`AGENTS.md` a pointer block that sends agents to the hubs directory to search for what they need: +A hub isn't a special file *type* — it's **any file the `hubs` glob matches that parses as a hub** +(a `---`-fenced `anchors:` frontmatter block + a markdown body). Claims don't have to live under +`hubs/`: add any file to the glob in `surf.toml`, give it the frontmatter, and `surf` treats it +like any other hub. The same is true for `AGENTS.md` or `CLAUDE.md`. + +The common question is `AGENTS.md` — the *imperative* operating instructions for coding agents, +versus hubs, which are *declarative* domain briefings. There are two approaches, and **a central +`hubs/` directory is the recommended default.** + +### Recommended — keep hubs and `AGENTS.md` separate + +Keep the two concerns apart: don't copy hub prose into `AGENTS.md`. Instead, give `AGENTS.md` a +pointer block that sends agents to the hubs directory to search for what they need: ```markdown @@ -139,16 +149,17 @@ Context lives in [`hubs/`](./hubs/) — read only the hub(s) you need. ``` -When that block is present, `surf lint` checks it links the configured hubs directory and that -the directory exists. It deliberately does **not** enumerate individual hubs — that would push an -agent to read everything instead of the one hub it needs. +When that block is present, `surf lint` checks it links the configured hubs directory and that the +directory exists. It deliberately does **not** enumerate individual hubs — that would push an agent +to read everything instead of the one hub it needs. + +This keeps `AGENTS.md` lean, stops the declarative and imperative content from drifting into each +other, and keeps verification metadata out of the file agents read as instructions. -## Claims in `AGENTS.md` / `CLAUDE.md` +### Alternative — fold claims into `AGENTS.md` / `CLAUDE.md` -A hub isn't a special file *type* — it's any file the `hubs` glob matches that parses as a hub -(frontmatter `anchors:` block + markdown body). So you *can* add `AGENTS.md` or `CLAUDE.md` to the -glob and give it hub frontmatter, making one file double as both agent instructions and verified -claims: +If you'd rather keep the instructions and the verified claims about them in one file, add it to the +glob and give it hub frontmatter: ```toml # surf.toml @@ -169,16 +180,32 @@ anchors: ``` `surf verify` then hash-checks that claim like any other hub, and `surf for ` reports -`AGENTS.md` as anchoring into it. Two things to know before you do this: - -- **The whole file must parse as a hub.** The `---` frontmatter has to be the top block and - unknown fields are rejected — you can't sprinkle a claim mid-document. Confirm whatever consumes - `AGENTS.md`/`CLAUDE.md` tolerates YAML frontmatter (most agent runners ignore it). -- **It couples the file to code structure.** Renaming an anchored symbol will trip the gate on +`AGENTS.md` as anchoring into it. Three things to weigh: + +- **The whole file must parse as a hub.** The `---` frontmatter has to be the top block and unknown + fields are rejected — you can't sprinkle a claim mid-document. +- **The frontmatter is part of what the agent reads.** Most agent runners load `AGENTS.md` / + `CLAUDE.md` as raw text and don't strip YAML frontmatter, so the `anchors:` block lands in the + agent's context as a little extra noise. It's small and structured, but if you want `AGENTS.md` + to stay purely instructions, prefer the recommended approach above. +- **It couples the file to code structure.** Renaming an anchored symbol trips the gate on `AGENTS.md` — that's the point of Surface, but it means an agent-docs file now participates in CI. -This is a trade-off, not a recommendation. The default split above — declarative hubs, imperative -`AGENTS.md` with a pointer block — keeps the two concerns separate; folding claims into -`AGENTS.md` is there if you'd rather keep the instructions and the claims about them in one file. +### When *not* to anchor a file + +"Any file can be a hub" doesn't mean every file should be. Pitch and marketing prose — a +`README.md` especially — is a poor fit: + +- **The claims are coarse.** A README describes behavior in broad strokes that span many symbols, + so an anchor either covers a near-whole-file span or trips on incidental edits — the + over-anchoring trap from [Choosing granularity](#choosing-granularity). +- **It usually duplicates hub prose.** The invariants a README restates should already be claimed + in the hubs anchored to the real code; anchoring the README just gives you a second copy to keep + honest. +- **On GitHub, frontmatter renders as a table.** GitHub displays a Markdown file's YAML frontmatter + as a metadata table at the top of the rendered page, so an `anchors:` block would sit above your + pitch on the repo's front page. + +Anchor the code, and let the README link to the docs instead. See also: [CI integration](./ci-integration.md) · [Examples](../examples.md). diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index b949801..f52111f 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -21,8 +21,10 @@ hubs = ["hubs/*.md", "docs/hubs/*.md", "**/_hub.md"] ``` Any file a glob matches is treated as a hub if it parses as one (frontmatter `anchors:` block + -markdown body), so the list can also pull in files that aren't named like hubs — for example -`AGENTS.md` (see [Claims in AGENTS.md / CLAUDE.md](../guides/authoring-hubs.md#claims-in-agentsmd--claudemd)). +markdown body), so claims can live in *any* file — not just files under `hubs/`. The list can pull +in files that aren't named like hubs, e.g. `AGENTS.md` or `CLAUDE.md` (see +[Where claims can live](../guides/authoring-hubs.md#where-claims-can-live) for the recommended +layout and the trade-offs). > **`surf new` uses only the first glob.** When scaffolding a hub it writes into the directory > derived from the first pattern (e.g. `docs/hubs/*.md` → `docs/hubs/`); the other patterns are