CityRP Documentation Repository

Docs layout migration

Last updated

Scope: repo layout, CI, tooling, link rewrites. No gamemode behaviour changes.

Goal

  1. Narrative docs live at docs/ (today src/docs/topics/), so the repo reads like every other repo: docs/modern-apis.md, docs/plugins.md, docs/clans/…, docs/design/….
  2. src/docs/ is deleted. Doc tooling is not "source"; it moves to tools/docs/.
  3. Every LDoc remnant that is tooling goes. LDoc tag handling in generate.py stays, because ~hundreds of Lua files still carry @tparam/@state/@warns and the generator is what keeps them rendering until each is converted (see doc-comments.md).

Inventory (what is in src/docs/ today — 48 tracked files)

Path What it is Fate
topics/*.md (25 files) Narrative docs, rendered to site + api.json → docs/*.md
topics/clans/*.md, topics/design/*.md Nested docs (api.json only, not HTML sidebar) → docs/clans/, docs/design/
generate.py, test_generate.py, fixtures/sample.json LuaLS JSON → HTML/api.json generator + tests → tools/docs/
requirements.txt (wenmode) Generator deps → tools/docs/requirements.txt
build.sh Installs node + sass + cssbeautify globally via sudo, compiles ll.scss, concatenates CSS delete (see §3)
style/ldoc-base.css Vendored LDoc layout CSS (#navigation/#content) → tools/docs/style/base.css (rename; keep content, fix header comment)
style/ll.scss + style/ll.css Theme, committed in both forms keep ll.css, delete ll.scss
style/glua.css Syntax colours → tools/docs/style/glua.css
.gitignore (build/, style.css, __pycache__/) Generated outputs → tools/docs/.gitignore, drop style.css entry once build.sh is gone
mcp/ (server, tests, Dockerfile, compose, README, requirements) cityrp-docs MCP server → tools/docs-mcp/

Nothing outside this tree imports the Python modules; the only consumers are the two CI workflows.

Why tools/docs/ and not docs/_build/ or similar

generate.py:load_topics() globs topics_dir/*.md for the HTML sidebar and rglob("*.md") for api.json. If tooling lived inside docs/, its README.md/mcp/README.md would be swept into the published topic index. Keeping tooling outside the content root means --topics docs needs no exclusion list. (src/ keeps its other build helpers — setmodel, visprot, enums.py — so tools/ is a new top-level dir; if that is unwanted, src/docs-tooling/ works identically.)

Reference map (every place that hard-codes src/docs)

Counts are occurrences of the literal src/docs.

Area Files Notes
CI (mirrored) .github/workflows/docs.yml (11), .gitea/workflows/docs.yml (10), .gitea/workflows/docs-mcp.yml (4) Paths filters, --doc_out_path, --topics, --style, publish dir, docker context
Lint gate (mirrored) .github/scripts/standards-diff.py (17), .gitea/scripts/standards-diff.py (17) Human-facing "(see src/docs/topics/x.md)" strings in rule messages
Agent guidance AGENTS.md (21), .github/copilot-instructions.md (8), .cursor/rules/*.mdc (13 across 6 files), .claude/agents/garrysmod-specialist.md (4), .claude/docs/technical-preferences.md (3) Plain link rewrites
Contributor docs CONTRIBUTING.md (6), .github/GOVERNANCE.md (1), .github/CODEOWNERS (2) CODEOWNERS path pattern must change or ownership silently lapses
Inside the docs topics/doc-comments.md (7), topics/luacats.md (6), mcp/README.md (11) Plus 22 ../ relative links in topics (casino.md, clans/bank.md, coding-standards.md, …) that assume depth 3 — each loses two ../
Lua source comments gamemode/sh_init.lua:113, entities/cityrp_laboratory/shared.lua:86 (@see), plugins/pcasino/sv_init.lua:42 Comment-only; the @see renders on the docs site, so it must be right
Plugin READMEs / notes perma_ents/README_3D_SIGNS.md, README_BILLBOARDS.md, problems/04-…, improvements/05-… Plain link rewrites
Generator itself generate.py header comment (2) Comment only

docs/ does not currently exist, so there are no collisions. .claude/docs/ is unrelated (Claude agent preferences) and stays.

Plan — one PR, five commits, in this order

Use git mv so history follows. Note that for Lua files a rename un-grandfathers the standards gate, but no Lua files are renamed here — only .md/.py/.css, which the gate ignores.

1. chore(docs): move topics to docs/

git mv src/docs/topics docs

Then fix the 22 ../ links (depth 3 → depth 1: ../../../x → ../x, ../../x → x). Verify with:

grep -rn "](\.\./" docs

Each remaining hit should resolve: cd docs && for f in $(grep -rho "](\.\./[^)#]*" . | sed 's/](//' | sort -u); do [ -e "$f" ] || echo MISSING $f; done.

2. chore(docs): move generator and MCP server to tools/

git mv src/docs/generate.py src/docs/test_generate.py src/docs/fixtures src/docs/requirements.txt src/docs/.gitignore tools/docs/
git mv src/docs/style tools/docs/style
git mv src/docs/mcp tools/docs-mcp
git rm src/docs/build.sh src/docs/style/ll.scss   # see §3
git mv tools/docs/style/ldoc-base.css tools/docs/style/base.css

src/docs/ is now empty and gone.

3. chore(docs): drop LDoc build tooling

  • build.sh existed to compile SCSS and concatenate CSS, installing node/sass/cssbeautify globally with sudo on the runner. ll.css is already committed next to ll.scss (and has drifted — last touched in different commits). Replace the whole script with a ~10-line --style-dir in generate.py that concatenates base.css → glua.css → ll.css into build/style.css itself. Keeps --style <file> working for anyone passing a prebuilt file.
  • Rename ldoc-base.css → base.css; rewrite its header comment to say it's the site's base layout (originally captured from LDoc output) rather than implying LDoc is still upstream.
  • Keep parse_ldoc_tags() and ParseLdocTagsTests in generate.py — they are the shim that renders the legacy @state/@warns/@bugs/@deprecated tags still present in Lua. Rename to parse_legacy_tags if you want the word gone; behaviour unchanged.
  • Keep the luacats-ldoc-tags rule in standards-diff.py — it's the guard that stops new LDoc tags, which is exactly what you want once the tooling is gone.
  • The three topic files that mention LDoc (coding-standards.md, doc-comments.md, luacats.md) and AGENTS.md ("LDoc is gone; src/docs/config.ld no longer exists") already describe it as dead; just update their paths.

4. ci(docs): point workflows at docs/ and tools/docs/

Both .github/ and .gitea/ copies — they are deliberately mirrored (self-hosted vs ubuntu, gh-pages vs codeberg pages action), so edit both, not one.

docs.yml (×2):

  • paths: → 'docs/**', 'tools/docs/**', and the workflow's own path.
  • drop the "Build CSS" step.
  • pip install -r tools/docs/requirements.txt
  • --doc_out_path=tools/docs/out (anything gitignored; don't write doc.json next to source).
  • python3 tools/docs/generate.py --json tools/docs/out/doc.json --output tools/docs/build --style-dir tools/docs/style --topics docs
  • upload/publish dir → tools/docs/build/.
  • Add a step python3 -m unittest tools/docs/test_generate.py — nothing runs it today.

docs-mcp.yml (gitea only):

  • paths: → 'tools/docs-mcp/**'; requirements, unittest -s, and docker context → tools/docs-mcp.

CODEOWNERS (both references): src/docs/ → docs/ and tools/docs/ + tools/docs-mcp/, otherwise review ownership for the tooling silently disappears.

Mechanical. From the repo root (Git Bash; these files are LF/ASCII-safe but confirm with git diff --stat that only intended files changed — several repo files are CRLF and sed -i will flip them; use the Edit tool or a Python byte-replace for any CRLF file):

grep -rIl --exclude-dir=.git --exclude-dir=node_modules "src/docs" . 

Replacement rules, applied in this order:

  1. src/docs/topics/ → docs/
  2. src/docs/mcp/ → tools/docs-mcp/
  3. src/docs/generate.py → tools/docs/generate.py
  4. any remaining src/docs/ → tools/docs/

Then rerun the grep — it must return nothing. Spot-check the three Lua comments and the standards-diff.py rule messages by eye (they're what contributors see in CI failures).

Verification checklist

  • grep -rI "src/docs" . → empty (excluding .git).
  • python3 -m unittest tools/docs/test_generate.py passes.
  • Local site build: install LuaLS, run the two commands from §4 by hand, open tools/docs/build/index.html — sidebar lists every top-level docs/*.md, topic pages render, api.json contains clans/… and design/… ids.
  • tools/docs-mcp tests: python -m unittest discover -s tools/docs-mcp -p 'test_*.py'.
  • Both lint gates clean on the branch (they only look at Lua under the gamemode, so expect no findings — but the @see comment edit in cityrp_laboratory/shared.lua is a Lua diff and will be checked).
  • After merge: docs.yml green on master, site at docs.limelightgaming.info updated; docs-mcp.yml green, cityrp-docs MCP list_topics still returns the full topic set (the server reads api.json from the deployed site, so it's a follow-on check, not a PR gate).
  • Contributor-facing MCP setup path in CONTRIBUTING.md / tools/docs-mcp/README.md matches the new location.

Things deliberately not in scope

  • Converting the remaining LDoc-tagged Lua comments (@tparam, @treturn, @usage, …). That is per-file work governed by the standards gate; the generator shim stays until it's done.
  • Renaming @namespace or changing how generate.py parses anything.
  • Moving problems/, improvements/, todo/, GLOSSARY.md, SECURITY-REVIEW-2026-08.md under docs/. They're working notes, not published docs, and pulling them in would put them on the public site (rglob feeds api.json). If they should become docs, that is a separate decision per file.
  • .claude/docs/ — not docs in this sense; leave it.