From 8a253e420e63d34a2a8a1246c9761b6b8ecee207 Mon Sep 17 00:00:00 2001 From: kbenestad Date: Fri, 8 May 2026 23:30:05 +0700 Subject: [PATCH] Revise CLAUDE.md for project updates and clarity Updated project documentation for MD-CMS, including changes to the CLI tool description, repository layout, and version markers. Adjusted folder names and clarified local preview instructions. --- CLAUDE.md | 125 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 91 insertions(+), 34 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 285f61f..8014c48 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,55 +6,92 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co MD-CMS is a markdown-based static site system with two distinct parts: -1. **`mdcms.py`** — a zero-dependency Python 3 CLI tool (standard library only). It scans content, generates `nav.yml` and `search.json`, validates config, and packages a zip for deployment. -2. **`website/index.html`** — a single-file browser renderer that reads markdown, config, and nav at runtime entirely client-side. There is no build pipeline, no compilation, no server. +1. **`mdcms.py`** — a Python 3 CLI tool (`click` + `PyYAML`). Manages a registry of sites, scans content, generates `nav.yml` and `search.json`, and is designed for both local use and GitHub Actions pipelines. +2. **`app/index.html`** — a single-file browser renderer that reads markdown, config, and nav at runtime entirely client-side. There is no build pipeline, no compilation, no server. -The `website/` folder is the deployable artifact. `mdcms.py` lives outside it. +The `app/` folder is the deployable artifact and the starter template downloaded when registering a new site. `mdcms.py` lives outside it. -## Running the CLI +## Repository layout -```bash -python3 mdcms.py +``` +mdcms.py ← CLI tool +pyproject.toml ← packaging (entry point, dependencies) +app/ + index.html ← renderer + v0.3 version marker + config.yml ← starter config + v0.3 version marker + nav.yml ← generated + search.json ← generated + pages/ + posts/ + assets/ +docs/ + banner/ + documentation.md + knownlimitations.md + quickstart.md +.github/workflows/release.yml ← cross-platform release builds +samplesite/ ← reference implementation (not deployed) ``` -This opens an interactive menu. Key options: +## CLI commands -| Option | Action | -|--------|--------| -| 1 | Validate config + nav, rebuild search.json, export `website.zip` | -| 2 | Interactive wizard to create `config.yml` and folder structure from scratch | -| 3 | Scan `pages/` and `posts/`, write `nav.yml` and `search.json` | -| 4 | Rebuild `search.json` only (faster, for content-only updates) | -| 5 | Check and interactively fix `config.yml` issues | -| 6 | Check and interactively fix `nav.yml` issues | -| 7 | Register/switch website paths (stored in `~/.mdcms/paths.json`) | -| 8 | Start `python3 -m http.server 8800` and open browser | +Install: `pip install mdcms` / `pipx install mdcms` — or use the standalone binary from a GitHub release. -**Local preview:** Always use option 8 (or run `python3 -m http.server 8800` in `website/`) rather than opening `index.html` directly — browsers block local file access due to CORS. +During development, run directly: `python3 mdcms.py ` + +| Command | Description | +|---|---| +| `mdcms register [path]` | Register a site. Downloads starter template from GitHub if no mdcms site is found at the path. Defaults to current directory. | +| `mdcms delete ` | Remove a site from the registry. Does not delete files. Prompts for confirmation. | +| `mdcms view` | List all registered sites with version and status. | +| `mdcms view ` | Show details: path, version, sitename, pages/posts count, sections, categories. | +| `mdcms build ` | Build `nav.yml` and `search.json` for a registered site. | +| `mdcms build --path ` | Build using an explicit path — no registry needed. Intended for CI/CD. | +| `mdcms build` | Build using current working directory. Simplest form for GitHub Actions. | + +**Local preview:** Run `python3 -m http.server 8800` in the site directory and open `http://localhost:8800`. Do not open `index.html` directly — browsers block local file access due to CORS. ## Architecture of `mdcms.py` -The file is a single-module Python script with these logical layers, in order: +Single-module Python script. Logical layers in order: -1. **Path registry** — `~/.mdcms/paths.json` stores named project paths. `active_path()` returns the currently selected project. -2. **Frontmatter parser** (`parse_frontmatter`) — reads `---` YAML blocks with a hand-rolled parser (no PyYAML). Returns a flat `{key: value}` dict with type coercion (bool, int, quoted strings). -3. **Category system** — category codes are parsed from `config.yml` by `parse_config_categories()`. Variant files use `basename..md` naming (e.g. `about.nb.md`). `identify_variant()` only recognises a suffix as a category code if it appears in the declared code list. -4. **Scanner** (`scan_and_categorize`) — walks `pages/` or `posts/`, skips drafts (`draft: true` in frontmatter), returns records including first 5000 chars of body for search indexing. -5. **Nav/search generators** — `generate_nav_yml()` and `generate_search_json()` produce the output files. `parse_nav_yml()` is a hand-rolled parser for reading back nav.yml (not a general YAML parser — only handles the exact format it emits). -6. **Validators** (`validate_config`, `validate_nav`) — return lists of `{field, issue, current}` dicts for display; used in options 1, 5, and 6. -7. **Menu actions** (`do_*` functions) — one per menu option, called by `main_menu()`. +1. **Version helpers** — `read_site_version()` reads the `mdcms v0.3` marker from the first line of `config.yml`. `version_status()` classifies sites as `ok`, `outdated`, `newer`, or `unsupported` against `MIN_SUPPORTED_VERSION`. +2. **Registry** — `~/.config/mdcms/sites.json` stores `{name: {path, version}}`. `load_registry()` / `save_registry()` / `resolve_site_path()`. +3. **Config reading** — `read_config()` reads `config.yml` with `yaml.safe_load()`. `get_category_info()` extracts category settings from the parsed dict. +4. **Frontmatter parser** (`parse_frontmatter`) — reads `---` YAML blocks using `yaml.safe_load()`. Returns `(meta_dict, body_text)`. +5. **Category system** — `identify_variant()` splits `.md` paths into `(base, category_code)`. A suffix is only treated as a category code if it appears in the declared code list. +6. **Scanner** (`scan_and_categorize`) — walks a directory, skips drafts, returns records with the first 5000 chars of body for search indexing. Paths are relative to `site_root`. +7. **Nav/search generators** — `generate_nav_yml()` emits a fixed-format YAML subset. `generate_search_json()` emits a JSON array. `merge_sections()` preserves existing section metadata on rebuild. +8. **Core build** (`run_build`) — orchestrates the full build: version check → config read → scan → merge → write nav.yml and search.json. +9. **Template download** (`download_template`) — fetches `app/` from GitHub via the Contents API using `urllib` (no extra dependencies). Recursively downloads files and directories. +10. **CLI commands** (`register`, `delete`, `view`, `build`) — implemented with `click`. Entry point: `main()` → `cli()`. -## Content structure inside `website/` +## Version markers + +Every mdcms site has a version marker on the first line of two files: + +- `config.yml` line 1: `# mdcms v0.3 | DO NOT REMOVE THIS COMMENT` +- `index.html` line 1: `` + +`register` and `build` both read the marker from `config.yml` to detect and validate the site. Sites with no marker are not recognised as mdcms sites. Sites below `MIN_SUPPORTED_VERSION` are rejected. + +There are two distinct version numbers: +- **CLI version** (`CLI_VERSION` in `mdcms.py`, `version` in `pyproject.toml`) — bumped with every release. +- **Site format version** (markers in `config.yml` and `index.html`) — only bumped when the site file format has a breaking change. Many CLI releases may share the same site format version. + +## Site structure + +The registered path points directly to the directory containing `index.html` (the site root). There is no `website/` subdirectory. ``` -website/ - index.html ← renderer (edit to change site behaviour) +/ + index.html ← renderer config.yml ← required: sitename, navigation; rest optional - nav.yml ← generated; manual edits to section metadata are preserved on rebuild + nav.yml ← generated; manual edits to section metadata are preserved search.json ← generated pages/ home.md ← default landing page - about.md ← default variant + about.md about.nb.md ← Norwegian variant (category suffix = nb) posts/ 2025-01-01-my-first-post.md @@ -113,15 +150,35 @@ paginate: yes Reliable tags (others are known-broken): `posts-datetime-chronological-byyearmonth`, `posts-datetime-reversechronological`. Use `datetime` frontmatter (format: `YYYY-MM-DD HH:MM`) for posts — `date` alone does not work reliably. +## Release workflow + +`.github/workflows/release.yml` triggers on version tags (`v*`). Uses a matrix of three runners: + +| Runner | Output | +|---|---| +| `ubuntu-latest` | `mdcms-linux-amd64` binary + `mdcms__amd64.deb` (via PyInstaller + fpm) | +| `macos-latest` | `mdcms-macos-arm64` binary | +| `windows-latest` | `mdcms-windows-amd64.exe` | + +All artifacts are attached to the GitHub release using `gh release create`. + +**Release checklist** — before tagging: +1. Update `CLI_VERSION` in `mdcms.py` +2. Update `version` in `pyproject.toml` +3. Update site format markers in `app/config.yml` and `app/index.html` only if the site format changed + +Then: `git tag v0.3.1 && git push origin v0.3.1` + ## Known limitations -- `mdcms.py` always expects the deployable site in a `website/` subdirectory of the registered project path. - Most `posts-*` tag variants are broken. Only `posts-datetime-chronological-byyearmonth` and `posts-datetime-reversechronological` reliably work. - Section headings in the nav are non-clickable (sections-sitemap is not yet implemented). ## Key implementation details -- `parse_nav_yml()` and `parse_config_categories()` are **not** general YAML parsers — they only handle the exact indentation/format that `mdcms.py` itself produces. Do not assume they handle arbitrary YAML. +- `generate_nav_yml()` emits a fixed-format YAML subset. It is **not** a general YAML emitter — do not assume it handles arbitrary structures. +- `yaml.safe_load()` is used for all YAML reading (config.yml, nav.yml, frontmatter). The nav.yml parser depends on PyYAML, not a hand-rolled parser. - Category code validation uses `CATEGORY_CODE_RE = re.compile(r"^[a-zA-Z0-9\-]+$")` — codes must match this. +- `scan_and_categorize()` takes both `directory` and `site_root` — paths in records are always relative to `site_root`. - The `samplesite/` directory is a reference implementation with multi-language categories (English, Norwegian, Arabic including RTL). It is not deployed; it exists for reference and testing. -- `website/` in the repo root is a minimal starter template (single page, no categories). +- Template download uses `urllib` (stdlib) only — no `requests` dependency.