diff --git a/.agents/skills/compile/SKILL.md b/.agents/skills/compile/SKILL.md index 96526a2..23f390d 100644 --- a/.agents/skills/compile/SKILL.md +++ b/.agents/skills/compile/SKILL.md @@ -16,7 +16,7 @@ Presets: `gcc-` / `clang-` + `debug` (coverage), `release`, `asan` (ASan + UBSan ## What CI builds on every PR -1. `clang-format` **19** over the tree (gates everything). Check `clang-format --version`: if it is not 19, ask the user to install it; a format diff seen only in CI points to the version gap. +1. `clang-format` at the version pinned in `build.yml` over the tree (gates everything). Check `clang-format --version`: if it differs, ask the user to install it; a format diff seen only in CI points to the version gap. 2. gcc and clang x amd64 and arm64, in `joinframework/join-ci`: `release` then `debug`; `asan` on amd64. 3. CodeQL on `gcc-debug`. 4. `gcovr` coverage on amd64 (gcc and clang, kept as artifacts); only gcc's is uploaded to Coveralls/Codecov/Codacy by `deploy-coverage`, which can fail the run. diff --git a/.agents/skills/conformance/SKILL.md b/.agents/skills/conformance/SKILL.md index 5a91025..ba12fef 100644 --- a/.agents/skills/conformance/SKILL.md +++ b/.agents/skills/conformance/SKILL.md @@ -7,17 +7,17 @@ description: Check dtoa/atod correctness versus main with the dconv-benchmark co Required at closing when conversion code changes (`dconv/include/dconv/`). No timing: any machine works. For speed see skill `performance`. -Clone `https://github.com/joinframework/dconv-benchmark.git` into `` under ``, never into the dconv tree. It pins dconv to a release tag, so point both builds at local checkouts, `origin/main` in a worktree: +`` is `dconv-benchmark` next to dconv (`../dconv-benchmark`); if missing, clone `https://github.com/joinframework/dconv-benchmark.git` there, never into the dconv tree. It pins dconv to a release tag, so point both builds at local checkouts, `origin/main` in a worktree: ```bash git -C fetch origin main && git -C worktree add --detach /base origin/main -cmake -S -B /build/main -DCMAKE_BUILD_TYPE=Release -DFETCHCONTENT_SOURCE_DIR_DCONV=/base -cmake -S -B /build/branch -DCMAKE_BUILD_TYPE=Release -DFETCHCONTENT_SOURCE_DIR_DCONV= +cmake -S -B /build/main -DCMAKE_BUILD_TYPE=Release -DFETCHCONTENT_SOURCE_DIR_DCONV=/base +cmake -S -B /build/branch -DCMAKE_BUILD_TYPE=Release -DFETCHCONTENT_SOURCE_DIR_DCONV= cmake --build --target conformance /benchmarks/conformance/conformance --output /conformance-.json ``` -Remove the worktree at the end (`git worktree remove --force`), even on failure. +Check each tree measures the intended dconv: `grep FETCHCONTENT_SOURCE_DIR_DCONV /CMakeCache.txt` must show its path, otherwise the numbers are the pinned tag. Remove the worktree at the end (`git worktree remove --force`), even on failure. ## Pass criteria diff --git a/.agents/skills/coverage/SKILL.md b/.agents/skills/coverage/SKILL.md index ed20a68..8b7e8d6 100644 --- a/.agents/skills/coverage/SKILL.md +++ b/.agents/skills/coverage/SKILL.md @@ -14,14 +14,14 @@ cmake --preset $C-debug && cmake --build --preset $C-debug find build/$C/debug -name '*.gcda' -delete ctest --preset $C-debug gcovr --root . --gcov-executable "$GCOV" --exclude-pattern-prefix LCOV --exclude-branches-by-pattern '.*' \ - --exclude '.*/tests/.*' --exclude 'tools/.*' --json-summary /$C-after.json --print-summary build/$C/debug + --exclude '.*/tests/.*' --exclude 'tools/.*' --json-summary /$C-${SIDE:-after}.json --print-summary build/$C/debug ``` -Baseline: same steps on `origin/main` in a worktree (`git fetch origin main && git worktree add --detach /base origin/main`) into `$C-before.json`; remove it at the end (`git worktree remove --force`), even on failure. +Baseline: same steps on `origin/main` in a worktree (`git fetch origin main && git worktree add --detach /base origin/main`) with `SIDE=before`, into `$C-before.json`; remove it at the end (`git worktree remove --force`), even on failure. Compare the total and every changed header. Any drop fails: add tests (or `LCOV_EXCL_*` for truly unreachable code, sparingly) and re-measure. -Needs `gcovr` and `llvm-cov` matching clang. Without them locally, save the steps above to `/coverage.sh` and run it in CI's image (`docker pull joinframework/join-ci:latest` first if it lacks `gcovr`), on copies so the host `build/` is untouched: +Needs `gcovr` and `llvm-cov` matching clang. Without them locally, save the steps above, after `set -e`, to `/coverage.sh` and run it in CI's image (`docker pull joinframework/join-ci:latest` first if it lacks `gcovr`), on copies so the host `build/` is untouched: ```bash rsync -a --exclude build ./ /head/ @@ -29,4 +29,4 @@ docker run --rm --user "$(id -u):$(id -g)" -v : -w /coverage.sh ``` -Same for the baseline with `-w /base`. If neither works, report coverage as **not verified**, never "not degraded". +Same for the baseline with `-e SIDE=before -w /base`. If neither works, report coverage as **not verified**, never "not degraded". diff --git a/.agents/skills/create-pr/SKILL.md b/.agents/skills/create-pr/SKILL.md index cee7594..7ce04d0 100644 --- a/.agents/skills/create-pr/SKILL.md +++ b/.agents/skills/create-pr/SKILL.md @@ -15,7 +15,7 @@ description: Branch, commit, push and open a PR with the dconv conventions. Use - `hotfix/`: small quick fix, minimal diff. Ask once if bugfix vs hotfix is unclear. -4. **Version**: bump `VERSION` in every PR: minor for a feature or an API change, patch otherwise (bugfix, hotfix, tools, CI, docs, agent config), major only on request. Minor or major also sets `PROJECT_NUMBER` = `.` in `doxyfile`. +4. **Version**: bump `VERSION` in every PR, unless it is already ahead of the latest tag at an equal or higher level: minor for a feature or an API change, patch otherwise (bugfix, hotfix, tools, CI, docs, agent config), major only on request. Minor or major also sets `PROJECT_NUMBER` = `.` in `doxyfile`. 5. **Commit**: explicit paths (no `git add -A`), one commit. Message: one capitalized imperative line, about 50 chars, no period, no body, no PR number, no trailer: `Stop installing tool headers`, or `Schubfach algorithm` for a new algorithm. 6. **Push**: `git push -u origin `. Never rewrite pushed history; report a rejected push. 7. **PR**: title = commit line, no body. Ask first; with `gh`: `gh pr create --base main --title "" --body ""`. Otherwise give `https://github.com/joinframework/dconv/compare/main...<branch>?expand=1`. diff --git a/.agents/skills/performance/SKILL.md b/.agents/skills/performance/SKILL.md index b6876ce..5ba0eb4 100644 --- a/.agents/skills/performance/SKILL.md +++ b/.agents/skills/performance/SKILL.md @@ -9,7 +9,7 @@ Required at closing when conversion code changes (`dconv/include/dconv/`). Done ## 1. Ask first -Ask the user: **is a real machine available (ideally x86_64 and aarch64)?** Clone `dconv-benchmark` there as in skill `conformance`, never into the dconv tree. +Ask the user: **is a real machine available (ideally x86_64 and aarch64)?** Use the `dconv-benchmark` checkout of skill `conformance`. ## 2. Performance mode @@ -25,10 +25,10 @@ Use `sudo` only when justified; otherwise give the commands with the `! ` prefix ## 3. Compare -Same `build/main` / `build/branch` setup as skill `conformance` (local checkouts via `FETCHCONTENT_SOURCE_DIR_DCONV`, `main` in a worktree). Build `bench_dtoa_dconv` and `bench_atod_dconv` (+ `bench_dtoa_dconvgrisu{,2,3}` if Grisu changed). +Same `<scratchpad>/build/main` / `<scratchpad>/build/branch` setup as skill `conformance` (local checkouts via `FETCHCONTENT_SOURCE_DIR_DCONV`, `main` in a worktree). Build `bench_dtoa_dconv` and `bench_atod_dconv` (+ `bench_dtoa_dconvgrisu{,2,3}` if Grisu changed). Binaries: `<build>/benchmarks/performance/bench_*`. Export the same `GLIBC_TUNABLES` as `scripts/run_bench.sh`. Run them alternately main/branch, several times, `taskset -c 2`, with `--benchmark_min_warmup_time=0.1 --benchmark_min_time=0.2s`; keep the minimum. Datasets: `Canada`, `Random`, `Unit`, `Integer`, `Digits01..17`. Remove the worktree at the end, even on failure. ## 4. Report -Machine (CPU, arch, governor/boost, native or WSL2), repetitions, and a before/after table per dataset with the relative change. A change is real only beyond the spread; never claim an unmeasured gain. +Machine (CPU, arch, governor/boost, native or WSL2), repetitions, and a before/after table per dataset with the relative change. A change is real only beyond the spread; never claim an unmeasured gain. When a delta is close to the spread, rerun with 5 repetitions per side and decide with google benchmark's `compare.py` (`<build>/_deps/googlebenchmark-src/tools/compare.py benchmarks <main>.json <branch>.json`, needs `scipy`; merge each side's runs into one JSON first): with 3 per side its Mann-Whitney test cannot reach p < 0.05. A mechanism read from the diff is a hypothesis until `perf stat -e cycles,instructions,cache-misses,branch-misses` shows it (needs `kernel.perf_event_paranoid` <= 1). diff --git a/.agents/skills/release/SKILL.md b/.agents/skills/release/SKILL.md index d3d4095..702a7ee 100644 --- a/.agents/skills/release/SKILL.md +++ b/.agents/skills/release/SKILL.md @@ -20,3 +20,4 @@ description: Tag and publish a dconv release from main. Use when asked to releas 5. **Downstream**: list the updates, each done in its own repo with its own PR, only if the user asks: - join: `find_package(dconv <version> REQUIRED)` in `data/CMakeLists.txt`, `find_dependency(dconv <version>)` in `cmake/joinConfig.cmake.in`, `libdconv-dev (>= <version>)` in `cmake/packaging.cmake`, the `.deb` URL and file name in `README.md`. - dconv-benchmark: `GIT_TAG v<version>` in `cmake/deps.cmake`, the dconv rows of the README libraries table (results and plots only after a new benchmark run). + - json-benchmark, pack-benchmark: `GIT_TAG v<version>` in `cmake/deps.cmake`. diff --git a/.claude/hooks/format.sh b/.claude/hooks/format.sh index 72bb680..27ff1c0 100755 --- a/.claude/hooks/format.sh +++ b/.claude/hooks/format.sh @@ -1,17 +1,17 @@ #!/usr/bin/env bash -missing () { - echo "format hook: $1 is missing, nothing was formatted. Ask the user to install it (AGENTS.md, Definition of done)." >&2 - exit 2 -} -input=$(cat) -case "$input" in *'.hpp"'*|*'.cpp"'*) ;; *) exit 0 ;; esac -command -v jq >/dev/null || missing jq -file=$(jq -r '.tool_input.file_path // empty' <<<"$input") -case "$file" in - *.hpp|*.cpp) - command -v clang-format >/dev/null || missing clang-format - [ -f "$file" ] || exit 0 - clang-format -i "$file" || { echo "format hook: clang-format failed on $file" >&2; exit 2; } - ;; -esac +re='"file_path": *"([^"]*\.(hpp|cpp))"' +sre='"session_id": *"([^"]*)"' +in=$(cat) +[[ $in =~ $sre ]] && mark=${TMPDIR:-/tmp}/format-hook-${BASH_REMATCH[1]} +[[ $in =~ $re ]] || exit 0 +file=${BASH_REMATCH[1]} +[[ $file == "$CLAUDE_PROJECT_DIR"/* ]] || exit 0 +v=$(sed -n "s/.*clang-format-version: *'\([0-9]*\)'.*/\1/p" "$CLAUDE_PROJECT_DIR"/.github/workflows/build.yml | head -1) +cf=clang-format-$v +command -v "$cf" >/dev/null || cf=clang-format +command -v "$cf" >/dev/null || { echo "format hook: clang-format is missing, nothing was formatted. Ask the user to install it (AGENTS.md, Definition of done)." >&2; exit 2; } +[ -f "$file" ] || exit 0 +"$cf" -i "$file" || { echo "format hook: $cf failed on $file" >&2; exit 2; } +have=$("$cf" --version | sed -n 's/.*version \([0-9]*\)\..*/\1/p') +[ -z "$v" ] || [ "$have" = "$v" ] || [ -e "$mark" ] || { touch "$mark"; echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PostToolUse\",\"additionalContext\":\"format hook: formatted with clang-format $have, CI uses $v; ask the user to install clang-format $v.\"}}"; } exit 0 diff --git a/.claude/rules/cpp-style.md b/.claude/rules/cpp-style.md index d350e00..e9c0a11 100644 --- a/.claude/rules/cpp-style.md +++ b/.claude/rules/cpp-style.md @@ -8,7 +8,7 @@ paths: # C++ style -`.clang-format` is enforced (hook, or `clang-format` 19); never hand-format. +`.clang-format` is enforced (hook, or `clang-format` at the CI version); never hand-format. ## Layout - C++20, header-only, no language extensions; GNU builtins and attributes already in use (`__builtin_expect`, `__attribute__ ((cold, noinline))`) are accepted. Space before every `(` (`foo (a)`, `TEST (s, n)`); Allman braces on every control statement. diff --git a/AGENTS.md b/AGENTS.md index 34ea5e6..1505102 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,7 +18,7 @@ cmake --preset gcc-debug && cmake --build --preset gcc-debug && ctest --preset g ## Agent files -Skills in `.agents/skills/<name>/SKILL.md`: compile, test, coverage, conformance, performance, add-gtest, gen-tables, create-pr, release; `<scratchpad>` in them is a temporary directory outside the repository. Run `clang-format` (CI uses 19) on changed C++ files. +Skills in `.agents/skills/<name>/SKILL.md`: compile, test, coverage, conformance, performance, add-gtest, gen-tables, create-pr, release; `<scratchpad>` in them is a temporary directory outside the repository. Run `clang-format` on changed C++ files at the version pinned in `.github/workflows/build.yml`. Changes to `AGENTS.md` and `.agents/` keep them tool-agnostic and short; `.claude/` is Claude Code only. diff --git a/VERSION b/VERSION index 23aa839..0495c4a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.2 +1.2.3