Docs layout migration
Last updated
May be stale
This page was last updated ago, on 3 October 2026. Check what it says against the code before relying on it.
Scope: repo layout, CI, tooling, link rewrites. No gamemode behaviour changes.
Goal
- Narrative docs live at
docs/(todaysrc/docs/topics/), so the repo reads like every other repo:docs/modern-apis.md,docs/plugins.md,docs/clans/…,docs/design/…. src/docs/is deleted. Doc tooling is not "source"; it moves totools/docs/.- Every LDoc remnant that is tooling goes. LDoc tag handling in
generate.pystays, because ~hundreds of Lua files still carry@tparam/@state/@warnsand 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.shexisted to compile SCSS and concatenate CSS, installing node/sass/cssbeautify globally withsudoon the runner.ll.cssis already committed next toll.scss(and has drifted — last touched in different commits). Replace the whole script with a ~10-line--style-diringenerate.pythat concatenatesbase.css→glua.css→ll.cssintobuild/style.cssitself. 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()andParseLdocTagsTestsingenerate.py— they are the shim that renders the legacy@state/@warns/@bugs/@deprecatedtags still present in Lua. Rename toparse_legacy_tagsif you want the word gone; behaviour unchanged. - Keep the
luacats-ldoc-tagsrule instandards-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) andAGENTS.md("LDoc is gone;src/docs/config.ldno 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 writedoc.jsonnext 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 dockercontext→tools/docs-mcp.
CODEOWNERS (both references): src/docs/ → docs/ and tools/docs/ + tools/docs-mcp/, otherwise review ownership for the tooling silently disappears.
5. docs: rewrite src/docs links across the repo
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:
src/docs/topics/→docs/src/docs/mcp/→tools/docs-mcp/src/docs/generate.py→tools/docs/generate.py- 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.pypasses. - Local site build: install LuaLS, run the two commands from §4 by hand, open
tools/docs/build/index.html— sidebar lists every top-leveldocs/*.md, topic pages render,api.jsoncontainsclans/…anddesign/…ids. -
tools/docs-mcptests: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
@seecomment edit incityrp_laboratory/shared.luais a Lua diff and will be checked). - After merge:
docs.ymlgreen on master, site at docs.limelightgaming.info updated;docs-mcp.ymlgreen,cityrp-docsMCPlist_topicsstill returns the full topic set (the server readsapi.jsonfrom 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.mdmatches 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
@namespaceor changing howgenerate.pyparses anything. - Moving
problems/,improvements/,todo/,GLOSSARY.md,SECURITY-REVIEW-2026-08.mdunderdocs/. They're working notes, not published docs, and pulling them in would put them on the public site (rglobfeedsapi.json). If they should become docs, that is a separate decision per file. .claude/docs/— not docs in this sense; leave it.