diff --git a/CLAUDE.md b/CLAUDE.md index 2c6e18606..02a293eba 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -70,6 +70,9 @@ Task-specific instructions are split into skill files under `skills/`. You MUST | `skills/version_update.md` | Bumping the daslang version number | | `skills/jobque_debugging.md` | Channel/LockBox/JobStatus/Feature leaks (`--track-job-status`, `DumpJobQueLeaks`) | | `skills/make_pr.md` | Creating a pull request (lint, test, AOT, format checklist) | +| `skills/preflight.md` | Pushing a non-trivial branch or reproducing a red CI lane — maps every PR-triggered CI lane to its exact local mirror command (or an honest "not mirrorable") | +| `skills/abi_break_sweep.md` | Changing public C++ API, AST node layout, or daslib generic signatures that external module repos compile against — both-worlds spellings, externals-merge-first ordering, daspkg-index scope | +| `skills/wsl_ci_repro.md` | Reproducing a Linux-only CI failure (sanitizers, POSIX divergence, headless timing) in the WSL CI-mirror distro — verbatim-CI recipe and its traps | | `skills/pr_review_iteration.md` | Working an open PR through CI failures and Copilot/human review feedback after the PR is created | | `skills/strudel_port.md` | Porting strudel.cc patterns into daslang | | `skills/clargs_usage.md` | Writing or editing any tool that parses command-line flags — declarative argv parsing via `daslib/clargs`, plus migration discipline for legacy `get_command_line_arguments()` callers | diff --git a/COVERAGE_GAP.md b/COVERAGE_GAP.md index 2e8110c45..5556cce8e 100644 --- a/COVERAGE_GAP.md +++ b/COVERAGE_GAP.md @@ -52,8 +52,17 @@ Process: per-stage plan → implement → review, same as FIXED_ARRAY_REWORK.md. lint + clang syntax-only pass on changed C++. `--full` adds interp/AOT/JIT suites, tests-cpp, all six doc gates, sequence smoke, CI-only-das compile sweep. Parallelize on big machines. +- **Cross-platform is a requirement, not a nicety**: the same tool must run on + Windows (primary dev box), macOS, and WSL/Linux. Implications: pure `.das` + + clargs + `fio` path helpers (no shell-specific constructs); syntax pass is + `clang-cl /Zs` on Windows and `clang -fsyntax-only` elsewhere behind one + flag; binary/build-dir discovery handles both MSVC multi-config + (`bin/Release/daslang.exe`) and single-config (`build/daslang`) layouts; + gates that need missing host tools (sphinx, clang) degrade to an explicit + SKIP with the install hint, never a silent pass. - clang syntax-only checker for changed `.cpp`/`.h` (`clang-cl /Zs` with - project includes) — the 80/20 for the MSVC-vs-clang diagnostic gap. + project includes on Windows, `clang -fsyntax-only` on mac/Linux) — the + 80/20 for the MSVC-vs-clang diagnostic gap. - Docs-gate runner mirroring doc.yml steps 1–6 exactly. - CI-only das surface list (dasOpenGL, sequence, release tooling …) compiled via `compile_check` with proper mounts. diff --git a/skills/abi_break_sweep.md b/skills/abi_break_sweep.md new file mode 100644 index 000000000..cf25fcec5 --- /dev/null +++ b/skills/abi_break_sweep.md @@ -0,0 +1,55 @@ +# ABI-break sweep — keeping external modules green + +Read this when a PR changes what external module repos compile against: +public C++ API under `include/daScript/`, AST node layout (fields added, +deleted, renamed — e.g. the fixed-array rework deleting `TypeDecl::dim`), +signatures of daslib generics that externals instantiate, or the +`.das_module` / serialization surface. + +## Why externals can red YOUR PR + +`extended_checks` runs `daspkg install dasImgui --branch master` — it builds +the external repo's **master** against **your branch's** headers, at PR time. +So a breaking change produces a red CI step that looks unrelated +(`no member named 'dim'` inside dasImgui), and the naive fix creates a +deadlock: the daslang PR can't go green until externals are compatible, and an +externals fix that *requires* the new daslang can't merge until the daslang PR +lands. + +The scope of "externals" is the [daspkg-index](https://github.com/borisbat/daspkg-index) +package list — that is the universe of repos any user (and the planned nightly +cron) builds against daslang master. + +## The checklist + +1. **Identify the breaking surface.** List every symbol removed, renamed, or + re-typed. In-repo impact: `cpp_grep_usage` / `grep_usage` MCP tools. +2. **Sweep external usage.** Grep each daspkg-index package (your local + checkouts, or GitHub code search) for those symbols — including + field-access shapes (`->dim`, `.dim`, `dimExpr`), not just call sites. +3. **Pick the compatibility strategy** — in preference order: + 1. **Both-worlds spelling** (the `isArray()` precedent). Find a predicate or + accessor that exists with the SAME meaning in both the old and new + world — `TypeDecl::isArray()` meant "is a fixed array" both pre-rework + (`(bool)dim.size()`) and post-rework (`baseType == tFixedArray`). + Rewrite the externals to that spelling: their PRs merge FIRST, + independently, green against both daslang versions. No feature macro, + no lockstep, no red-master window. + 2. **Shim in daslang** — keep the old name as a forwarding alias for one + release when no shared spelling exists. + 3. **Feature macro / version check** in the external — last resort; it + forces lockstep merges and rots into dead branches. +4. **Land order: externals first.** Because extended_checks pulls externals' + master, the external fix must be MERGED (not just PR'd) before the daslang + PR's CI can go green. Merge externals → re-run the daslang PR's + extended_checks → merge daslang. +5. **Verify locally before pushing** — the external-module junction pattern + (`skills/external_module_debugging.md`), rebuild order **daslang first, + then each module**. Stale-DLL trap: a `.shared_module` built against the + old daslang fails to load *silently* and surfaces as a misleading + `error[20605]: missing prerequisite` — after rebuilding daslang, delete the + external module's `_build/` and rebuild before trusting any 20605. +6. **Post-merge sweep.** Build every remaining daspkg-index package against + the new master and fix drift (unrelated rot surfaces here too — budget for + it). The nightly index cron (`COVERAGE_GAP.md` Stage 4) will turn this + into a standing signal. diff --git a/skills/make_pr.md b/skills/make_pr.md index 3dd031fee..fa4504749 100644 --- a/skills/make_pr.md +++ b/skills/make_pr.md @@ -150,6 +150,15 @@ bin/Release/daslang.exe -jit tests/decs/test_bulk_create.das 2>&1 | grep -iE "ve **What this catches:** struct-layout drift (i32 vs i64 fields in JIT mirror), mixed-width arithmetic (i64 × i32), missing intrinsic lowering after a runtime-side widening. The two suggested tests cover array indexing + table operations; widen the smoke list to `tests/soa/test_soa_basic.das` and `tests/language/typeAlias.das` if you touched generic-instance lowering or capture frames. +## 2.7. Type-system / daslib-generics changes — sequence smoke + externals sweep + +If the PR changes the type system, generic binding rules, AST node layout, or widely-instantiated daslib generics (`builtin.das`, `safe_addr.das`, …), two CI gates have no overlap with the standard test suite: + +1. **Sequence smoke** — the only pre-merge lane that compiles GLFW-gated `.das` (dasOpenGL helpers etc.). Build the runtime module targets and run `examples/games/sequence/ci_smoke_test.ps1` (`.sh` on POSIX) — exact commands in `skills/preflight.md`. +2. **Externals sweep** — `extended_checks` installs external dasImgui from ITS master against your branch; an ABI break vs external repos reds CI on an unrelated-looking step. Follow `skills/abi_break_sweep.md` (both-worlds spellings, externals-merge-first ordering, daspkg-index scope). + +Skip for changes that can't alter what external/module-gated code sees (tests-only, docs-only, tool-local). + ## 3. Build and run AOT tests **IMPORTANT:** Kill the MCP server and any running daslang processes first — they lock build output files. @@ -184,9 +193,12 @@ Use `timeout: 0` (no timeout) for the cmake build — it can take 2-25 minutes. - Public functions in `daslib/*.das` (added, removed, renamed, or signature changed) - `//!` doc-comments in `daslib/*.das` files - C++ bindings in `modules/*/src/*.cpp` or `src/builtin/*.cpp` that add new public functions, types, or struct fields +- **Struct fields or enum values added, REMOVED, or reordered** in any C++ type documented under `doc/source/stdlib/handmade/` — das2rst validates handmade docs **positionally** (line 1 = type description, line N+1 = Nth field/value), so a removed field is just as CI-fatal as an added one - RST files in `doc/source/` (handwritten tutorials, reference pages, TOCs) - `doc/reflections/das2rst.das` or `doc/reflections/rst.das` +Note that CI's doc workflow triggers on **any** `daslib/**` or `src/builtin/**` change and runs six gates (`skills/preflight.md` has the full list); das2rst **stops at the FIRST validation panic**, so a single CI round can hide N−1 further issues — loop step 4b locally until it runs clean. + **Which substeps to run** — match what changed, not "all in order": | Changed | Run | @@ -236,12 +248,13 @@ grep -c Uncategorized doc/source/stdlib/generated/*.rst | grep -v ':0$' Must return empty. If not, go back to step 4a and add the missing function to a group. -### 4f. Clean Sphinx build +### 4f. Clean Sphinx build — BOTH builders -MUST delete cache — cached builds hide errors: +CI runs `sphinx-build -W` for **latex AND html** — they catch different warning sets (latex chokes on some table/unicode constructs html accepts). MUST delete cache — cached builds hide errors: ```bash -rm -rf doc/sphinx-build site/doc +rm -rf doc/sphinx-build site/doc build/latex +sphinx-build -W --keep-going -b latex -d doc/sphinx-build doc/source build/latex sphinx-build -b html -d doc/sphinx-build doc/source site/doc 2>&1 | sed 's/\x1b\[[0-9;]*m//g' | tee /tmp/sphinx_out.txt tail -3 /tmp/sphinx_out.txt grep -iE "warning:|error:" /tmp/sphinx_out.txt @@ -259,9 +272,13 @@ Common Sphinx issues: - **Malformed table**: Grid/simple table column widths don't align - **Unexpected indentation**: Content after directive must be indented consistently -### 4g. Stage doc changes +### 4g. Stage doc changes — including NEW generated files -Add all changed/new files in `doc/` and `doc/reflections/` to the commit. For squashed branches, amend the existing commit. +Add all changed/new files in `doc/` and `doc/reflections/` to the commit. For squashed branches, amend the existing commit. CI fails if das2rst generated files that aren't tracked — verify: + +```bash +git ls-files --others --exclude-standard doc/source/stdlib/ # must be empty +``` See `skills/documentation_rst.md` for full details on doc conventions, tutorial RST, and cross-references. @@ -273,17 +290,11 @@ Run the MCP `format_file` tool on all changed `.das` files in a single batched c Do NOT format files you didn't change — only format files that are part of the PR. -### CI `das-fmt` ≠ MCP `format_file` — verify named-arg spacing - -CI's `extended_checks` job runs `./bin/Release/daslang ./das-fmt/dasfmt.das -- --path ./ --verify` (plus a compiled `das-fmt.exe` pass). The `das-fmt/dasfmt.das` file is NOT in the repo tree — CI fetches it externally — and it is **stricter** than MCP `format_file` on at least one rule: - -| MCP `format_file` accepts | CI `das-fmt` requires | -|---|---| -| `Foo(a=1, b=2)` | `Foo(a = 1, b = 2)` (spaces around `=` in named args) | +### CI formatter = in-tree `utils/das-fmt/dasfmt.das` -`bin/Release/das-fmt.exe` built from `utils/dasFormatter/` is the **v1→v2 syntax converter**, NOT the same formatter — running it locally does not reproduce CI. +CI's `extended_checks` runs `./bin/daslang ./utils/das-fmt/dasfmt.das -- --path ./ --verify` — the script is **in the repo tree** and wraps the same `daslib/das_source_formatter` engine as MCP `format_file`, so the two agree (probe-verified: both rewrite `Foo(a=1)` → `Foo(a = 1)`). The pre-push hook runs the exact CI command on tracked files; if the hook passes, the CI formatter gate passes. -**Before pushing:** mentally format named-arg constructor / call sites with spaces around `=`. If CI `extended_checks` fails on a format diff after MCP said "already formatted", fix the spacing and re-push (or amend, on a squashed branch). +**Name trap:** a locally built `bin/Release/das-fmt.exe` (the CMake `das-fmt` target, from `utils/dasFormatter/`) is the **v1→v2 syntax converter**, not the formatter. CI's `das-fmt.exe` verify pass works because CI first overwrites that binary with an `-exe`-compiled `dasfmt.das`. Locally, always invoke the formatter as ` utils/das-fmt/dasfmt.das -- ...` (or MCP `format_file`). ## 6. Create the PR @@ -302,9 +313,10 @@ Stage, commit, push, and create the PR using GitHub MCP tools or `gh` CLI. Follo | Workaround audit | `git diff origin/master..HEAD` — read every changed file | Smell (redundant step / synthetic≠real / special-case / copied-hack) → surface fix-vs-workaround and **ask**; never ship a buried workaround | | Tests | `dastest -- --test tests/` | Must pass. Fix own, fix obvious pre-existing, ask about unclear | | JIT smoke | `daslang.exe -jit .das 2>&1 \| grep -iE "verifier\|Both operands"` | Empty output = pass. Windows `clang-cl` link fail is local-only, ignore | +| Type-system/generics | sequence smoke (`skills/preflight.md`) + externals sweep (`skills/abi_break_sweep.md`) | Only for type-system / AST-layout / daslib-generics changes | | AOT build | `cmake --build build --config Release --target test_aot -j 64` | Kill daslang first. Register new test dirs | | AOT tests | `test_aot.exe -use-aot dastest/dastest.das -- --use-aot --test tests` | Same as regular tests | -| Docs | `das2rst.das` + stubs + Sphinx | Only if daslib/C++ bindings/RST changed | +| Docs | `das2rst.das` (loop until clean) + stubs + Uncategorized + untracked + Sphinx latex AND html | Any daslib/src-builtin/RST change triggers all six gates — `skills/preflight.md` | | Format | MCP `format_file` with comma-separated list or glob of changed `.das` files (single call) | Only changed files | | Wrap-up curation | `skills/task_wrap_up.md` | Optional. Add answers for cache misses, edit cached answers this PR invalidated | | `.md` stop | `git diff --name-only origin/master..HEAD \| grep '\.md$'` | If any match: STOP, list changes, ask user to review BEFORE push | diff --git a/skills/preflight.md b/skills/preflight.md new file mode 100644 index 000000000..bcc201fc7 --- /dev/null +++ b/skills/preflight.md @@ -0,0 +1,148 @@ +# Preflight — CI lane ↔ local mirror + +Read this before pushing a non-trivial PR, or when a CI lane goes red and you +need to reproduce it. Every CI failure during the fixed-array rework (PR #3095) +was an **oracle mismatch** — a gate CI enforces that no local step mirrored — +not a wrong change. This file maps every PR-triggered lane to its exact local +mirror, or says honestly that there isn't one. + +A `utils/preflight` tool automating the common tiers is planned +(`COVERAGE_GAP.md` Stage 2); until it lands, these are the manual commands. + +**Conventions.** `` = your compiler binary: `bin/Release/daslang.exe` +(Windows MSVC multi-config), `bin/daslang` (Ninja single-config — what CI's +extended_checks uses on all three OSes), or `build/daslang` (Make/Ninja +without `EXECUTABLE_OUTPUT_PATH`). Commands are platform-neutral unless marked. +"WSL" means the verbatim-CI recipe in `skills/wsl_ci_repro.md` — fresh clone at +the CI ref, never a working-tree copy. + +## What runs on every PR + +| Workflow | Trigger | Jobs | +|---|---|---| +| `build.yml` | every PR | `build` matrix (5 targets × Debug/Release/RelWithDebInfo × sanitizers), `bundle_smoke`, `build_windows_mingw`, `build_windows_clangcl` | +| `extended_checks.yml` | every PR | linux + darwin15-arm64 + windows, ALL release modules ON | +| `wasm_build.yml` | every PR | emscripten build of `web/` on 3 OSes + `wasm_cross` | +| `build_eastl.yml` | every PR | EASTL shadow-config build + no-fileio build (linux clang) | +| `doc.yml` | only if `doc/**`, `daslib/**`, or `src/builtin/**` changed | six doc gates | +| `playground-e2e.yml` | only if `site/**` / `web/examples/ui/**` changed | Playwright on the web playground | + +## build.yml — the build matrix + +Per-lane steps: build → JIT prewarm → JIT test sweep → interpreter sweep → +`ctest -L small` → AOT sweep (Release lanes only). + +| CI step | Local mirror | Notes | +|---|---|---| +| Interpreter sweep | ` dastest/dastest.das -- --color --failures-only --timeout 900 --test tests` | | +| JIT sweep | ` dastest/dastest.das -jit -- --jit-opt-level=3 --color --failures-only --timeout 900 --test tests` | Windows-local `clang-cl` link failures are env noise — the catchable class is LLVM verifier errors; full end-to-end JIT needs WSL/mac. See `skills/make_pr.md` §2.5 for the 2-test smoke version | +| Small C++ tests | `ctest --test-dir build --build-config Release -L small --output-on-failure` | drop `--build-config` on single-config generators. **Run this after touching `tests-cpp/`** — and remember MSVC tolerates C++ that clang/gcc reject (the doctest bit-field incident); see `skills/writing_cpp_tests.md` | +| AOT sweep | `cmake --build build --config Release --target test_aot`, then `bin/Release/test_aot.exe -use-aot dastest/dastest.das -- --use-aot --color --failures-only --timeout 900 --test tests` | Release lanes only in CI | +| Debug lanes | same suite against a **Debug build** | Debug bypasses the fused interpreter permutations — a fix that lands only in the fused path passes Release everywhere and trips Debug; conversely fused-path bugs need Release. If you touched `src/simulate/simulate_fusion_*`, run both configs | +| Sanitizer lanes (linux Release asan/tsan/ubsan) | WSL: `CC=clang CXX=clang++ cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DDAS_USE_SANITIZER=`, then the JIT sweep on `tests/language` | not mirrorable on Windows/mac. CI applies LSan suppressions (`format_error`, `uriParseSingleUriA`, `uriMakeOwner`) — see the workflow's Test step | +| linux_arm / darwin lanes | mac: same commands as linux. From Windows: not mirrorable | ARM-specific reds (LLVM SelectionDAG, alignment) are CI-only signals | + +## build.yml — bundle_smoke (linux) + +Release-modules build → `cmake --install --prefix ./daslang_bundle --strip` → +`bash ci/smoke_test_bundle.sh ./daslang_bundle`. WSL-mirrorable verbatim; run +it when touching CMake `install(...)` rules, `ci/release_modules.txt`, or +module loading. This is the lane that catches install-layout regressions. + +## build.yml — build_windows_mingw + +msys2 CLANG64 build with dasClangBind + dasLLVM ON, full interp/JIT/AOT +sweeps, plus two things no other lane runs: the `bind_clangbind.das` +self-binder freshness check (`git diff --exit-code -- modules/dasClangBind/src/`) +and `test_const_preproc.das`. A local msys2 mirror is possible but rarely worth +it — the 80/20 for "compiles under MSVC, breaks under clang" is a clang +syntax-only pass on your changed C++ (see clang-cl below). If you regenerated +dasClangBind bindings, run the self-binder locally per `skills/clang_bind_build.md`. + +## build.yml — build_windows_clangcl + +Fully mirrorable on a Windows dev box, in a separate (gitignored) build dir: + +```powershell +cmake -B build-clangcl -G "Visual Studio 17 2022" -A x64 -T ClangCL -DCMAKE_BUILD_TYPE=Release +cmake --build build-clangcl --config Release --parallel +``` + +The cheap tier — seconds, catches most MSVC-vs-clang diagnostics without a +full build — is a syntax-only pass on just the files you changed: + +```powershell +# from a VS dev prompt (clang-cl on PATH); add -I flags for daScript includes +clang-cl /Zs /std:c++17 -Iinclude -I3rdparty/fmt-10.1.0/include .cpp +``` + +(mac/WSL equivalent: `clang -fsyntax-only -std=c++17 -Iinclude ... .cpp`.) + +## extended_checks.yml + +**The defining divergence: CI configures with ALL release modules ON** — +`ci/release_modules.txt` flips `DAS_HV/LLVM/AUDIO/PUGIXML/SQLITE/GLFW_DISABLED=OFF`. +A typical local dev build has several OFF, so module-gated `.das` and C++ +(dasOpenGL helpers, dasHV-dependent daslib code) is compiled by CI and by +nothing on your machine. For any change to daslib generics or the type system, +mirror the configure: + +```bash +cmake -B build -DDAS_HV_DISABLED=OFF -DDAS_LLVM_DISABLED=OFF -DDAS_AUDIO_DISABLED=OFF \ + -DDAS_PUGIXML_DISABLED=OFF -DDAS_SQLITE_DISABLED=OFF -DDAS_GLFW_DISABLED=OFF +``` + +| CI step | Local mirror | Notes | +|---|---|---| +| dasgen freshness | ` dasgen/gen_bind.das` then `git diff --exit-code -- include/daScript/builtin/` | regen + commit if dirty; see `skills/visitor_gen_bind.md` | +| Run examples | `cmake --build build --config Release --target run_examples` | | +| Utils tests | `cmake --build build --config Release --target run_utils_tests` | | +| Tutorial dry-runs | `cmake --build build --config Release --target dry_run_tutorials` | catches compile rot in `tutorials/` — run after daslib API changes | +| Standalone exes | `cmake --build build --config Release --target all_utils_exe`, plus ` -exe -output bin/das-fmt utils/das-fmt/dasfmt.das` and `... bin/das-lint utils/lint/main.das` | `-exe` needs dasLLVM + lld-link on PATH | +| Sequence smoke | Windows: `pwsh examples/games/sequence/ci_smoke_test.ps1 "$(pwd)"`; linux/mac: `bash examples/games/sequence/ci_smoke_test.sh "$(pwd)"` | build the runtime modules first: `cmake --build build --config Release --target dasModuleGlfw dasModuleLiveHost dasModuleHV dasModuleAudio dasModulePUGIXML dasModuleStbImage`. **This is the only pre-merge lane that compiles GLFW-gated `.das` like dasOpenGL** — run it for type-system / daslib-generics changes | +| Formatter `--verify` | the pre-push hook runs it exactly (tracked files via `--files-from`); manual: ` utils/das-fmt/dasfmt.das -- --path ./ --verify` | name trap: a locally built `bin/Release/das-fmt.exe` is the **v1→v2 converter** (`utils/dasFormatter/`), not the formatter — CI overwrites its copy with an `-exe`-compiled `dasfmt.das` before verifying | +| Lint changed `.das` | the pre-push hook, or `git diff --name-only origin/master..HEAD -- '*.das' \| xargs utils/lint/main.das -- --quiet` | zero warnings required | +| daslang_static sweep | `cmake --build build --config Release --target daslang_static`, then `bin/Release/daslang_static.exe dastest/dastest.das -- --color --failures-only --test tests` | rarely built locally; catches static-registration / no-dynamic-modules divergence | +| Ser/deser sweep | ` dastest/dastest.das -- --test tests --ser serialized.bin` then `... --deser serialized.bin` | run after touching AST serialization (`ast_serializer.cpp`, flag-bit additions) | +| MCP tools test | ` dastest/dastest.das -- --color --failures-only --test utils/mcp/test_tools.das` | linux-only in CI but runs anywhere; MCP signature changes break it silently — run after editing `utils/mcp/` | +| dasImgui install | ` utils/daspkg/main.das -- install dasImgui --branch master` | **the externals-coupling gate**: CI builds external dasImgui from ITS master against YOUR branch — an ABI break vs externals reds this on an unrelated-looking step. See `skills/abi_break_sweep.md` | +| Coverage | ` dastest/dastest.das -- --cov-path coverage.lcov --color --test tests/language --timeout 1800` + `dascov` | rarely needed locally | + +## doc.yml — the six gates + +Only triggered when `doc/**`, `daslib/**`, or `src/builtin/**` changed — but +`daslib/**` means **any daslib edit** runs all six. CI stops at the FIRST +das2rst panic, so one CI round can hide N-1 further issues — loop gate 1 +locally until clean. Needs a daslang built with `DAS_HV_DISABLED=OFF` and +`DAS_PUGIXML_DISABLED=OFF` (das2rst documents those modules). Step-by-step +workflow: `skills/make_pr.md` §4; conventions: `skills/documentation_rst.md`. + +| # | Gate | Local mirror | +|---|---|---| +| 1 | das2rst runs clean (positional handmade-doc validation panics on count mismatch) | ` doc/reflections/das2rst.das` — repeat until no panic | +| 2 | no `// stub` in handmade docs | `grep -rl '// stub' doc/source/stdlib/handmade/` → must be empty | +| 3 | no `Uncategorized` sections | `grep -rl '^Uncategorized$' doc/source/stdlib/generated/` → must be empty; fix via `group_by_regex` in das2rst.das | +| 4 | no untracked generated RST | `git ls-files --others --exclude-standard doc/source/stdlib/` → must be empty; `git add` the new files | +| 5 | LaTeX sphinx, warnings-as-errors | `sphinx-build -W --keep-going -b latex -d doc/sphinx-build doc/source build/latex` | +| 6 | HTML sphinx, warnings-as-errors | `sphinx-build -W --keep-going -b html -d doc/sphinx-build doc/source build/site` — delete `doc/sphinx-build` first; cached builds hide errors | + +(The PDF compile after gate 6 is `continue_on_error` — not a gate.) + +## wasm_build.yml + +`wasm_build`: emsdk build of `web/` + a Node hello-world. `wasm_cross`: +cross-compiles utility mains to wasm32 via dasLLVM and runs them under +wasmtime, with emscripten **pinned to 5.0.3** (newer clang crashes on +`utils/dasFormatter/ds_parser.cpp` diagnostics). At risk when touching the +parser (`.ypp` → huge generated `.cpp`), `web/`, or the dasLLVM cross-compile +pipeline. Local mirror = emsdk in WSL following the workflow verbatim; for +most changes, let CI carry this lane. + +## build_eastl.yml + +Builds daslang against EASTL (`cmake/das_config_eastl/` shadow config) and a +second time with `DAS_NO_FILEIO=1`, where a shadow `` header +`#error`s on any stray include. At risk when adding `#include ` +outside fio, new vsnprintf/locale dependencies, or touching +`cmake/das_config*`. WSL-mirrorable with the workflow's exact commands; +otherwise just keep `` includes inside the fio layer.