Follow-up to #31: the doc/lint/grammar checks moved to Ludic there; this ports
the remaining docgen piece (gen.py / check.py / palette.py) so nothing in the
documentation pipeline is Python any more.
Three new `x` subcommands, all in Ludic and compiled by Ludic:
- x docs-gen [--out DIR] the static-site generator: parses docs/language/**
front-matter + bodies (fences, Parameters:), builds the section/symbol
model, reads the asset templates, and emits every page + ns/color/api pages
+ the landing page + ludic-highlight.js + symbols.json + .nojekyll.
- x docs-check [DIR] the coverage / integrity guard (required files, a
page per inventory.json symbol, duplicate-token and one-dir-per-namespace
guards, highlighter link targets).
- x docs-palette the named-colour source of truth: the palette table
moved into tools/x/docgen.ludic, emitting emit_color.ludic (pointer, not
ptr) + palette.json.
Verified against the Python oracle: `x docs-gen` reproduces all 466 output files
BYTE-FOR-BYTE (a Ludic json.dumps/html.escape/front-matter port — ordered dicts,
indent=2 vs compact, ensure_ascii \uXXXX, codepoint-aware truncation), and
`x docs-check` matches check.py's pass/fail output. Wired into `x test` as a
gate (docs-gen -> docs-check on a fresh site; docs-palette stays byte-identical).
CI swap: ci.yml and docs.yml call the Ludic generator; docs.yml drops the
python:3.12 container and bootstraps the toolchain from the IR seed instead.
tools/docgen/{gen,check,palette}.py deleted; only assets/ + inventory.json
remain. Completes #31's criterion 3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`new` accepted only a bare `new T` (every field its declared default) or
`new []T`, but the docs (kw-new) document `new Record { field: value, … }`
as the way to construct a record with non-default fields — a documented,
intended form the parser never accepted (`let o = new Point { x: 3 }` failed
with "expected newline or ';'").
Parse an optional `{ … }` override record after the type in a `new`
expression (reusing the existing `record()` parser that `spawn` uses), and
seed each field in emit_new_struct from that record when present, else from
the field's declared default. `new []T` and bare `new T` are unchanged.
Also mark the illustrative kw-import fence `# doc-check: skip` (its imports
are example paths that can't resolve in isolation), which makes `x check-docs`
fully green (398 fences, 0 drifted) — so it is now wired as a gate in
`x test-tools` and CI, guarding against future doc/compiler drift.
Reseed is a clean fixpoint (x bootstrap-cfree holds); x test (56),
x selfhost-test (29, golden renders unchanged) and x test-tools (30) green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the Python doc/lint/vocabulary guards with Ludic equivalents that
run through the `x` task runner, so the checks need no Python interpreter:
x check-docs every ```ludic doc fence parses (or is marked)
x check-impl every implemented feature has a docs/language page
x check-vocabulary vocabulary in sync across grammar / lexer / header / parser
x lint-asset <file> validate one editor .json / .xml asset
New fragments: tools/x/json.ludic (a small JSON reader — objects/arrays/
strings with \uXXXX + surrogates/numbers/literals, used by the vocabulary
check's grammar navigation and the asset validator) and tools/x/checks.ludic
(the checks + string helpers + a minimal XML well-formedness validator).
`x test-tools` now runs the vocabulary + docs-coverage checks and the
JSON/XML asset validation through Ludic instead of python3; ci.yml's
docs-coverage step calls `x check-impl` / `x check-vocabulary`. Each port was
verified against its former Python script for exact verdict parity on the
clean tree and on injected drift (a removed keyword, a broken grammar
alternation, an undocumented method).
Deletes the superseded scripts: tools/check-vocabulary.py, tools/check-docs.py,
tools/docgen/check-impl.py, tools/docgen/validate.py. The docgen site
generator (gen.py/check.py/palette.py) and the LSP protocol driver
(test-lsp.py) remain and are tracked separately.
Toolchain unchanged (seed byte-identical); `x test` (56) and `x test-tools`
(29) stay green.
Part of #31
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The convention was documented in CONTRIBUTING.md but nothing enforced it, and no
decision was on record for the pre-self-hosting `Phase` history.
- tools/git-hooks/commit-msg — rejects a summary that is not a Conventional
Commit. tools/git-hooks/lib.sh holds the single rule (types, scope, `!`, and
the merge/revert/autosquash exemptions) so the hook and CI cannot drift.
- tools/git-hooks/lint-range.sh — lints a commit range with that same rule.
- .forgejo/workflows/commit-lint.yml — runs it over the new commits on every
push and PR, as the backstop for contributors who have not enabled the hook.
- Fix the pre-commit hook, which pointed at the old build/ludic-fmt path (the
toolchain moved to bin/) and so silently no-op'd; it now finds bin/ludic-fmt.
- CONTRIBUTING.md — full type/scope table, the one-line enable
(`git config core.hooksPath tools/git-hooks`), and a **Git history** section
recording the decision: leave the pushed `Phase`-era history as-is (a rewrite
is destructive and non-reversible for anyone who cloned); enforce the
convention going forward; let #33's first release tag double as the clean `v0`
baseline that brackets the old prefix without touching a commit.
Verified: the hook accepts feat/fix/ci/refactor(!)/merge/revert and rejects
"added regex" / "Fix bug" / "WIP"; lint-range passes recent history and flags
the old `Phase 8b:` commit.
Closes#34
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Until now the only workflow was docs.yml — nothing gated a change on the
compiler even building, on `x test` / `x test-tools`, or on the headline C-free
self-rebuild reproducing the seed. Add two Forgejo Actions workflows on the same
`docker` runner the docs job uses.
The toolchain is macOS-first: the self-hosted compiler emits the Darwin libc
standard-stream globals (`__stdoutp`/`__stderrp`), the one thing that stops its
IR from linking on Linux. Everything else is portable — clang-16 assembles the
seed cleanly and the C-free bootstrap reproduces it byte-for-byte on Linux too.
So rather than require a macOS runner (none is registered), bridge that single
gap with a tiny **C-free LLVM-IR shim** (tools/ci/linux_stdio_shim.ll) that
defines the Darwin-named globals over glibc's stdout/stderr, injected into every
clang link via LUDIC_CC. The language keeps its no-C-compiler guarantee.
Workflows:
- ci.yml — bootstrap the toolchain from the seed, then `x test` + `x test-tools`
+ the docs-cover-the-implementation checks, on push to main and PRs.
- bootstrap.yml — `x bootstrap-cfree`: assert the seed rebuilds itself
byte-for-byte (returns non-zero on drift).
Make the suites host-aware so a Linux run is green without hiding anything: a
new is_darwin()/skip() pair (tools/x/prelude.ludic) makes the cases that are
genuinely macOS-ABI bound — the Cocoa-windowed `ludicc -o` link, the golden
render hashes (blessed on macOS; text raster differs by a hair elsewhere), the
Os known-folder/uname surface, Fs.list and the LSP workspace walk (both read the
BSD dirent layout) — print a visible `skip` off Darwin instead of failing. On
macOS every one of them still runs: suites stay 56 / 29 / 29 green there, and
run 51 / 28 (+skips) on Linux, bootstrap-cfree byte-identical on both.
The formatting gate is ludic-fmt *idempotence* (already in `x test-tools`), not
`fmt(x) == x`: this codebase deliberately preserves hand alignment, so a strict
"already formatted" check would fight that contract.
A prebuilt CI image with clang-16 + python3 baked in is the obvious follow-up
speed-up (ties into the packaging work in #33).
Closes#32
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor onboarding for the self-hosted toolchain (issue #37):
- `CONTRIBUTING.md`: prerequisites, the bootstrap one-liner, the dev loop
(`bin/x reseed` -> `bin/x bootstrap-cfree` -> `bin/x test`), stdlib-addition
guidance, and the code/commit conventions (Conventional Commits, ludic-fmt,
one-job-per-file, Ludic-not-C/Python/JS for new tooling).
- `.forgejo/issue_template/`: bug, proposal, and cleanup/DX templates.
- `.forgejo/pull_request_template.md`: a checklist covering tests, the
bootstrap fixpoint, formatting, docs/inventory, and commit style.
- `.forgejo/CODEOWNERS` and a short `CODE_OF_CONDUCT.md`.
Closes#37
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The docs workflow sat in "Waiting" indefinitely — "no online runner found
matching this label: ubuntu-latest". Our Forgejo runner advertises the
`docker` label (and is Docker-capable, so the container: python:3.12 step
still works); `ubuntu-latest` is a GitHub-ism it never registered. Point
runs-on at the label the runner actually has.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
node:20-bookworm (the runner image) has no python3 and setup-python could not
resolve; run the pure-Python generator in a python image and git-clone the
public repo instead of relying on node-based actions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the hardcoded landing page and minimal reference with a generated
documentation site driven by a single source of truth.
- docs/language/**: one file per symbol (93 keywords/types/builtins/namespace
methods/operators/annotations), each with front-matter (id, kind, tokens,
sig, tip) + description + a ```ludic example. Seeded by exploding the former
inline SECTIONS list; these files are now the source of truth.
- docs/site/: site.json (editable hero/features/showcase/messaging, not
hardcoded) + snippets/*.ludic (real programs shown on the landing page).
- tools/docgen/gen.py: generates index.html, api.html, ludic-highlight.js and
symbols.json. The highlighter's symbol tables, hover tips and jump anchors
are GENERATED from the per-symbol files — add a symbol and it is recognized,
tipped and linked in every snippet automatically. Python stdlib only.
- tools/docgen/check.py: verifies the pages contract + that no snippet token
links to a missing reference anchor.
- .forgejo/workflows/docs.yml: rebuilds and publishes to the pages branch on
every push to main touching the docs sources.
Consumes the new Screen.*/Color.*/named-arg API and the 221-color palette.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>