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
113 changes: 60 additions & 53 deletions .github/workflows/docs-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,65 +17,90 @@ jobs:
- name: Check out repository
uses: actions/checkout@v4

- name: Validate maintained Markdown structure
- name: Validate Markdown structure
shell: bash
run: |
python - <<'PY'
from pathlib import Path
import re
import sys

files = [
"README.md",
"CONTRIBUTING.md",
"STYLE_GUIDE.md",
"visual-guides.md",
"arista.md",
"aws.md",
"azure.md",
"cisco.md",
"docker.md",
"git.md",
"google-cloud.md",
"iptables.md",
"kubernetes.md",
"leaf-spine.md",
"linux-boot.md",
"pulumi.md",
"rest-api.md",
"spanning-tree.md",
"terraform.md",
]
required_root = {
Path("README.md"),
Path("CONTRIBUTING.md"),
Path("STYLE_GUIDE.md"),
}
allowed_root_markdown = {path.name for path in required_root}
allowed_domains = {
"networking",
"network-platforms",
"cisco-contact-center",
"cloud",
"containers",
"automation",
"linux",
"high-availability",
"development",
}

docs = sorted(Path("docs").rglob("*.md"))
files = sorted(required_root) + docs

supported_mermaid = re.compile(
r"^(flowchart|graph|sequenceDiagram|stateDiagram-v2|"
r"classDiagram|erDiagram|journey|gitGraph|mindmap|timeline)\b"
)

errors = []
for filename in files:
path = Path(filename)

unexpected_root = {
path.name
for path in Path(".").glob("*.md")
if path.name not in allowed_root_markdown
}
if unexpected_root:
errors.append(
"Unexpected root Markdown files: "
+ ", ".join(sorted(unexpected_root))
)

if not docs:
errors.append("docs/: no Markdown reference files found")

for path in docs:
relative = path.relative_to("docs")
if len(relative.parts) == 1:
if relative.name != "visual-guides.md":
errors.append(
f"{path}: only visual-guides.md may live directly under docs/"
)
elif len(relative.parts) != 2:
errors.append(f"{path}: references must use docs/<domain>/<file>.md")
elif relative.parts[0] not in allowed_domains:
errors.append(f"{path}: unknown documentation domain")

for path in files:
if not path.is_file():
errors.append(f"{filename}: file is missing")
errors.append(f"{path}: file is missing")
continue

text = path.read_text(encoding="utf-8")
if not text.strip():
errors.append(f"{filename}: file is empty")
errors.append(f"{path}: file is empty")
continue

first_content_line = next(
(line for line in text.splitlines() if line.strip()),
"",
)
if not first_content_line.startswith("# "):
errors.append(f"{filename}: first content line must be an H1")
errors.append(f"{path}: first content line must be an H1")
if "\x00" in text:
errors.append(f"{filename}: NUL byte found")
errors.append(f"{path}: NUL byte found")

for number, line in enumerate(text.splitlines(), start=1):
if re.search(r"\[[^\]]*\]\(\s*\)", line):
errors.append(f"{filename}:{number}: empty Markdown link")
errors.append(f"{path}:{number}: empty Markdown link")

opening_count = text.count("```mermaid")
mermaid_blocks = re.findall(
Expand All @@ -84,7 +109,7 @@ jobs:
flags=re.DOTALL,
)
if opening_count != len(mermaid_blocks):
errors.append(f"{filename}: unclosed Mermaid code fence")
errors.append(f"{path}: unclosed Mermaid code fence")

for index, block in enumerate(mermaid_blocks, start=1):
first_line = next(
Expand All @@ -93,15 +118,15 @@ jobs:
)
if not supported_mermaid.match(first_line):
errors.append(
f"{filename}: Mermaid block {index} has an unsupported "
f"{path}: Mermaid block {index} has an unsupported "
f"or missing diagram declaration: {first_line!r}"
)

if errors:
print("\n".join(errors))
sys.exit(1)

print(f"Validated {len(files)} maintained Markdown files.")
print(f"Validated {len(files)} Markdown files across {len(docs)} references.")
PY

links:
Expand All @@ -110,30 +135,12 @@ jobs:
- name: Check out repository
uses: actions/checkout@v4

- name: Check maintained documentation links
- name: Check documentation links
uses: lycheeverse/lychee-action@v2
with:
args: >-
--offline
--verbose
--no-progress
README.md
CONTRIBUTING.md
STYLE_GUIDE.md
visual-guides.md
arista.md
aws.md
azure.md
cisco.md
docker.md
git.md
google-cloud.md
iptables.md
kubernetes.md
leaf-spine.md
linux-boot.md
pulumi.md
rest-api.md
spanning-tree.md
terraform.md
fail: true
'./**/*.md'
fail: true
50 changes: 49 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,46 @@ Corrections and focused additions are welcome. The goal is operationally useful,
4. Mark destructive commands clearly and include a safer inspection step first.
5. Never commit real credentials, internal addresses, customer data, private architecture, or employer-specific interview material.

## Choose the correct location

Reference sheets belong under a shallow domain directory:

```text
docs/<domain>/<topic>.md
```

Current domains are:

- `networking`
- `network-platforms`
- `cisco-contact-center`
- `cloud`
- `containers`
- `automation`
- `linux`
- `high-availability`
- `development`

Use the closest existing domain instead of creating a one-file directory. Add a new domain only when multiple related references justify it. Keep `README.md`, `CONTRIBUTING.md`, `STYLE_GUIDE.md`, and `LICENSE` at the repository root.

## Name the file intentionally

Use the canonical name engineers are likely to search for:

- common, unambiguous protocol acronyms: `tcp.md`, `udp.md`, `ospf.md`, `lldp.md`;
- descriptive names for ambiguous concepts: `spanning-tree.md`, `osi-model.md`;
- actual platform names: `arista-eos.md`, `cisco-ios.md`, `fortigate.md`, `junos.md`;
- concise product acronyms inside an already specific folder: `docs/cisco-contact-center/cvp.md`.

Do not rename files solely to make everything an acronym or solely to spell everything out. Optimize for clarity, searchability, and domain context.

When adding, moving, or renaming a sheet:

1. update the root README index;
2. update all relative cross-references;
3. keep the hierarchy shallow;
4. ensure documentation-quality checks pass.

## Cheat-sheet structure

Use the following order when it fits the topic:
Expand Down Expand Up @@ -39,6 +79,14 @@ A read-only, least-disruptive-first sequence.
- [Documentation title](https://example.com)
```

When inherited content has only received a structural cleanup and has not been technically revalidated, use:

```markdown
> **Status:** Legacy reference — technical validation pending
```

Do not assign a fresh review date to unvalidated content.

## Style rules

- Use one `#` heading per document.
Expand All @@ -58,4 +106,4 @@ A pull request should explain:
- which official references were used;
- how the examples were validated.

For a simple typo or broken link, a short explanation is sufficient.
For a simple typo or broken link, a short explanation is sufficient.
Loading
Loading