summaryrefslogtreecommitdiff
path: root/AGENTS.md
blob: bb9da750046ce3932d4ffb0a31c329b2899e9b3d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# AGENTS.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project

VyOS user documentation, built with Sphinx and hosted on Read the Docs at
https://docs.vyos.io. This branch (`sagitta`, 1.4.x previous LTS) is migrated
to MyST Markdown (`.md`) except for two pages with known converter issues that
stayed in RST: `docs/cli.rst` and `docs/installation/cloud/aws.rst`.
`source_suffix` in `docs/conf.py` is `['.rst', '.md']`. Both formats are
first-class to Sphinx.

Pre-migration RST shadows of converted pages are archived under
`docs/_rst_legacy/` for reference only — they are excluded from the build,
not consulted by Sphinx, and must not be edited.

## Build

```bash
# Docker (recommended — bundles Sphinx and the MyST/RTD plugin set)
docker build -t vyos/vyos-documentation docker
docker run --rm -it -v "$(pwd)":/vyos -w /vyos/docs \
  -e GOSU_UID=$(id -u) -e GOSU_GID=$(id -g) \
  vyos/vyos-documentation make html

# Live-reload server on port 8000
docker run --rm -it -p 8000:8000 -v "$(pwd)":/vyos -w /vyos/docs \
  -e GOSU_UID=$(id -u) -e GOSU_GID=$(id -g) \
  vyos/vyos-documentation make livehtml

# Local (Python 3, see requirements.txt for pinned versions)
pip install -r requirements.txt
cd docs && make html
```

Output: `docs/_build/html/`.

## Lint

The repo doesn't ship a local lint config or pin a linter binary. CI runs
`vyoslinter` (`scripts/doc-linter.py` in this repo, invoked from
`.github/workflows/lint-doc.yml`) on changed files only — see the CI
section below. For local checks, manually grep for the rules in
[Source conventions](#source-conventions) (line length, address space,
suppression markers).

## Branches and versions

One long-lived branch per VyOS release line. Branch names are constellations sorted by area:

| Branch | VyOS version |
|--------|--------------|
| `rolling` | rolling / 1.5+ (default branch — all new docs target this) |
| `circinus` | 1.5.x |
| `sagitta` | 1.4.x |
| `equuleus` | 1.3.x (legacy) |
| `crux` | 1.2.x (legacy) |

PRs target `rolling`. After merge, request backports via Mergify comments on the PR:

```
@Mergifyio backport circinus
@Mergifyio backport sagitta
```

Mergify is configured at the org level (no `.mergify.yml` in the repo). The PR template has a `## Backport` section to declare intent.

## Architecture

### Sphinx config (`docs/conf.py`)

- `source_suffix = ['.rst', '.md']` — both formats build into the same site.
- MyST extensions: `colon_fence`, `deflist`, `fieldlist`, `substitution`.
- `myst_fence_as_directive = ["cfgcmd", "opcmd", "cmdincludemd"]` — MyST fences with these names get parsed as if they were RST directives. This is how command pages stay format-portable.
- Custom modules live in `docs/_ext/` (only files listed in `extensions = [...]` in `conf.py` are actual Sphinx extensions; the others are support scripts loaded ad hoc):
  - `vyos.py` (Sphinx extension, registered as `vyos`) — defines the `cfgcmd`, `opcmd`, `cmdinclude`, `cmdincludemd`, `cfgcmdlist`, `opcmdlist` directives and `cfgcmd`/`opcmd` roles that drive command coverage tracking.
  - `autosectionlabel.py` (Sphinx extension, registered as `autosectionlabel`) — connects to `doctree-read` to register sections as labels.
  - `testcoverage.py` — standalone helper that reads VyOS XML command definitions and exposes coverage stats; not a Sphinx extension.
  - `releasenotes.py` — standalone release-notes/changelog generator script; not a Sphinx extension.

### Source files

- `docs/<subdir>/<page>.md` — canonical MD source for migrated pages
  (most of the tree).
- The two RST-only pages on this branch: `docs/cli.rst` and
  `docs/installation/cloud/aws.rst`. Both stayed RST due to converter issues
  at MyST-migration time and have not been revisited since.
- `docs/_include/<name>.txt` — shared RST snippets included into MyST
  pages via `cmdincludemd`. Their content is parsed as RST so the
  legacy templates keep working unchanged.
- `docs/_rst_legacy/<subdir>/rst-<page>.rst` — archived pre-migration RST
  shadows of converted pages. Excluded from the Sphinx build and from the
  Context7 index. Reference only.

**Editing rules:**
- Existing migrated page (has `<page>.md`): edit the `.md`. Do not touch the
  archived shadow under `_rst_legacy/`.
- One of the two remaining RST-only pages above: edit the `.rst`.
- New page: write it as `.md` from the start. The `md-` prefix that earlier MyST migration commits used is gone — never add it.

### Command directives

`.. cfgcmd::`, `.. opcmd::`, and `.. cmdinclude::` are the VyOS-specific Sphinx directives in RST. They are tracked for command coverage — do **not** convert them to plain `.. code-block::`. In MyST the same directives are written as fenced blocks: ```` ```{cfgcmd} set system ... ```` (enabled by `myst_fence_as_directive`). The MyST include directive is named `cmdincludemd` (not `cmdinclude`) so that template parsing follows MyST rules in MD pages and RST rules in RST pages — pick `cmdinclude` in `.rst`, `cmdincludemd` in `.md`.

## Source conventions

### RST heading hierarchy

```
##### Title (overline+underline, one per file)
***** Chapters
===== Sections
----- Subsections
^^^^^ Subsubsections
""""" Paragraphs
```

The first heading in every RST file uses `#` overline+underline. Field lists (e.g., `:lastproofread:`) or labels may precede it.

### Formatting

- 80-character line limit (exception: inside `.. code-block::` / fenced code blocks — `<pre>` preserves source verbatim).
- American English.
- Indent with 2 spaces.
- Blank lines around headings.
- Inline code: use double backticks per RST convention (the [Inline markup](https://docutils.sourceforge.io/docs/user/rst/quickref.html#inline-markup) section of the docutils quick reference).

### IP addresses (linter-enforced)

Allowed without suppression:
- RFC 5737 IPv4 docs: `192.0.2.0/24`, `198.51.100.0/24`, `203.0.113.0/24`
- RFC 3849 IPv6 docs: `2001:db8::/32`
- RFC 1918 private ranges: `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`
- Loopback (`127.0.0.0/8`), link-local (`169.254.0.0/16`), `0.0.0.0/0`

Allowed ASN: `64496-64511` (16-bit), `65536-65551` (32-bit).
Allowed MAC ranges: `00-53-00`–`00-53-FF` (unicast), `90-10-00`–`90-10-FF` (multicast).

**Requires `stop/start_vyoslinter` suppression:**
- Real public IPs (e.g., `8.8.8.8` for DNS examples).
- NAT64 well-known prefix `64:ff9b::/96`.
- Lines over 80 chars (URLs, certificate fingerprints).

### Linter suppression markers

```rst
.. stop_vyoslinter

.. code-block:: none

   content with real IPs or long lines here

.. start_vyoslinter
```

In MyST `.md` files use the comment form `% stop_vyoslinter` / `% start_vyoslinter` for top-level Markdown content. Inside `{eval-rst}` blocks (where the embedded content is parsed as RST) keep the RST form `.. stop_vyoslinter` / `.. start_vyoslinter` — the linter scans the source line literally and only the form that matches the surrounding parser is recognized. Likewise, `.txt` template files (included via `{include}` or `cmdincludemd`) keep the RST form.

Markers must always come in pairs. Indentation may match the surrounding directive (indented inside a block) or sit at column 0 (top-level) — both are valid.

### Configuration page structure

1. **Theory** — what it is, when to use it, relevant RFCs.
2. **Configuration** — all CLI options as `.. cfgcmd::` (RST) or ```` ```{cfgcmd} ```` (MD).
3. **Examples** — practical configurations with topology diagrams.
4. **Known issues** — problems and workarounds.
5. **Debugging** — log collection, `show` commands, state indicators.

### `.. TODO::` markers

Two valid uses:
1. **Tracking** marker on pages that still need `cfgcmd`/`opcmd` conversion — intentional.
2. **Stale** marker on pages that already have full content — should be removed.

A PR that both adds and removes TODOs is not contradictory; intent matters.

## LLM-Facing Files (`llms.txt`, `llms-full.txt`)

Both files are regenerated on every `html` and `readthedocs` builder run.
The `dirhtml` builder is intentionally skipped — production publishes
only via `html`/`readthedocs`, and we don't render `llms.txt` for builds
we don't ship. Local `make dirhtml` is a developer convenience and
won't emit `llms.txt`.

Files are shipped at the docs root for each version
(`https://docs.vyos.io/en/<version>/llms.txt`, `.../llms-full.txt`).

- **`llms-full.txt`** — auto-generated by the `sphinx_llms_txt` extension from
  the full corpus. No curation; configured by `llms_txt_file = False` (which
  disables the extension's *index* output, not the full output).
- **`llms.txt`** — curated overview rendered at build time from
  `docs/_templates/llms.txt.j2`. URLs and the version line are interpolated
  from `html_baseurl` and `release` so the file always matches the branch.
  The render lives in `_write_llms_txt(app, exception)` in `docs/conf.py`,
  wired via `app.connect('build-finished', ...)`.

When adding new top-level sections to the docs, add a corresponding bullet in
`docs/_templates/llms.txt.j2`. Branch-specific differences (e.g. sagitta has
no `vpp/index.md` or `contributing/index.md`) live in that branch's copy of the template.

## Read the Docs Layout

RTD slugs as of 2026-05-04 (verified via API). Re-verify via the RTD
Versions API (project `vyos`) and update the date stamp before editing this
table.

| Slug | Verbose | Branch | Role |
|---|---|---|---|
| `rolling` | rolling | `rolling` | canonical for rolling/next major |
| `1.5` | circinus | `circinus` | canonical for current LTS |
| `1.4` | sagitta | `sagitta` | canonical for previous LTS |
| `1.3`, `1.2` | equuleus, crux | older | canonical for older releases |

URL-level redirect aliases (resolve to the canonicals above):
`/en/latest/* → /en/rolling/`, `/en/lts/* → /en/1.5/`,
`/en/stable/* → /en/lts/`, `/en/circinus/* → /en/1.5/`,
`/en/sagitta/* → /en/1.4/`, `/en/equuleus/* → /en/1.3/`,
`/en/crux/* → /en/1.2/`.

`html_baseurl` per branch must point at the canonical (numeric or `rolling`),
not the alias, so `<link rel="canonical">` and the sitemap match what RTD
serves and crawlers skip the redirect hop.

## CI

- **vyoslinter** (`scripts/doc-linter.py` in this repo, invoked via
  `.github/workflows/lint-doc.yml`) — line length and IP rules, on
  changed files only.
- **Sphinx build** — runs on Read the Docs for every PR; preview URL appears as a check.
- **CLA check** — contributors must sign the VyOS CLA before merge.
- **Conflict check** — fails the PR if it doesn't merge cleanly into base.