From 89333b8cb10edefd93a92e4a74cf951c3fc4c441 Mon Sep 17 00:00:00 2001 From: mrabine Date: Wed, 11 Nov 2026 17:40:33 +0100 Subject: [PATCH 1/5] Setup agent --- .agents/skills/add-gtest/SKILL.md | 19 +++++++++++++++ .agents/skills/compile/SKILL.md | 28 ++++++++++++++++++++++ .agents/skills/conformance/SKILL.md | 32 +++++++++++++++++++++++++ .agents/skills/coverage/SKILL.md | 24 +++++++++++++++++++ .agents/skills/create-pr/SKILL.md | 22 +++++++++++++++++ .agents/skills/gen-tables/SKILL.md | 30 +++++++++++++++++++++++ .agents/skills/performance/SKILL.md | 34 ++++++++++++++++++++++++++ .agents/skills/release/SKILL.md | 22 +++++++++++++++++ .agents/skills/test/SKILL.md | 21 ++++++++++++++++ .claude/agents/explorer.md | 28 ++++++++++++++++++++++ .claude/agents/reviewer.md | 27 +++++++++++++++++++++ .claude/hooks/format.sh | 15 ++++++++++++ .claude/rules/cpp-style.md | 32 +++++++++++++++++++++++++ .claude/rules/power-tables.md | 15 ++++++++++++ .claude/rules/tests.md | 19 +++++++++++++++ .claude/settings.json | 37 +++++++++++++++++++++++++++++ .claude/skills/add-gtest | 1 + .claude/skills/compile | 1 + .claude/skills/conformance | 1 + .claude/skills/coverage | 1 + .claude/skills/create-pr | 1 + .claude/skills/gen-tables | 1 + .claude/skills/performance | 1 + .claude/skills/release | 1 + .claude/skills/test | 1 + AGENTS.md | 37 +++++++++++++++++++++++++++++ CLAUDE.md | 1 + 27 files changed, 452 insertions(+) create mode 100644 .agents/skills/add-gtest/SKILL.md create mode 100644 .agents/skills/compile/SKILL.md create mode 100644 .agents/skills/conformance/SKILL.md create mode 100644 .agents/skills/coverage/SKILL.md create mode 100644 .agents/skills/create-pr/SKILL.md create mode 100644 .agents/skills/gen-tables/SKILL.md create mode 100644 .agents/skills/performance/SKILL.md create mode 100644 .agents/skills/release/SKILL.md create mode 100644 .agents/skills/test/SKILL.md create mode 100644 .claude/agents/explorer.md create mode 100644 .claude/agents/reviewer.md create mode 100755 .claude/hooks/format.sh create mode 100644 .claude/rules/cpp-style.md create mode 100644 .claude/rules/power-tables.md create mode 100644 .claude/rules/tests.md create mode 100644 .claude/settings.json create mode 120000 .claude/skills/add-gtest create mode 120000 .claude/skills/compile create mode 120000 .claude/skills/conformance create mode 120000 .claude/skills/coverage create mode 120000 .claude/skills/create-pr create mode 120000 .claude/skills/gen-tables create mode 120000 .claude/skills/performance create mode 120000 .claude/skills/release create mode 120000 .claude/skills/test create mode 100644 AGENTS.md create mode 100644 CLAUDE.md diff --git a/.agents/skills/add-gtest/SKILL.md b/.agents/skills/add-gtest/SKILL.md new file mode 100644 index 0000000..8ec798e --- /dev/null +++ b/.agents/skills/add-gtest/SKILL.md @@ -0,0 +1,19 @@ +--- +name: add-gtest +description: Add and register a gtest file for a dconv header or class. Use when creating tests for new or untested code. +--- + +# Add a gtest + +1. Read the header and a neighbouring test (e.g. `dconv/tests/schubfach_test.cpp`) for the license header, includes and `main`. +2. Create `dconv/tests/
_test.cpp` (or `_test.cpp` when a header holds several classes) with one `TEST (, )` per function (suite = namespace, header or class; constructors in `create`, assignments in `assign`), each documented with a three-line `/**` / ` * @brief test.` / ` */` block and covering success, errors, boundaries, overloads and edge doubles (`±0`, subnormals, `DBL_MIN`/`DBL_MAX`, halfway cases, > 19 digits, `inf`/`nan`, invalid input). +3. Register it in `dconv/tests/CMakeLists.txt` next to its neighbours: + + ```cmake + add_executable(.gtest _test.cpp) + target_link_libraries(.gtest ${PROJECT_NAME} GTest::gtest_main pthread) + gtest_discover_tests(.gtest) + ``` +4. Build and run that target only with `gcc-debug` (skills `compile` and `test`). + +If a test reveals a bug, report the failing assertion and propose a diff; do not change the code under test unprompted. diff --git a/.agents/skills/compile/SKILL.md b/.agents/skills/compile/SKILL.md new file mode 100644 index 0000000..96526a2 --- /dev/null +++ b/.agents/skills/compile/SKILL.md @@ -0,0 +1,28 @@ +--- +name: compile +description: Configure and build dconv with the CMake presets, as CI does. Use to compile a change or fix build errors. +--- + +# Compile + +Dev loop vs closing: see `AGENTS.md` "Definition of done". + +```bash +cmake --preset +cmake --build --preset [--target .gtest] +``` + +Presets: `gcc-` / `clang-` + `debug` (coverage), `release`, `asan` (ASan + UBSan); trees in `build//`. Fix warnings (`-Werror`), never silence them. + +## 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. +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. + +arm64 is not reproducible locally: flag endianness and `char` signedness assumptions. `__uint128_t` must stay behind `#if defined(__SIZEOF_INT128__)` with a portable `#else`; no CI job compiles that `#else` (gcc and clang define it on amd64 and arm64), so check it by reading the code. `__builtin_expect` via `DCONV_LIKELY` is accepted. + +## Report + +Presets built, with the first error or warning in full on failure. diff --git a/.agents/skills/conformance/SKILL.md b/.agents/skills/conformance/SKILL.md new file mode 100644 index 0000000..b410450 --- /dev/null +++ b/.agents/skills/conformance/SKILL.md @@ -0,0 +1,32 @@ +--- +name: conformance +description: Check dtoa/atod correctness versus main with the dconv-benchmark conformance suite. Use at closing when conversion code changed. +--- + +# Conformance + +Required at closing when conversion code changes (`dconv/include/dconv/`). No timing: any machine works. For speed see skill `performance`. + +Ask where `dconv-benchmark` is cloned (`https://github.com/joinframework/dconv-benchmark`); never clone it into the dconv tree. It pins dconv to a release tag, so point both builds at local checkouts, `main` in a worktree: + +```bash +git -C worktree add --detach /base 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 --build --target conformance +/benchmarks/conformance/conformance --output /conformance-.json +``` + +Remove the worktree at the end (`git worktree remove --force`), even on failure. + +## Pass criteria + +Compare the `dconv*` rows of both runs (the other libraries are context): + +- **Dtoa Roundtrip**: 100% for every dconv algorithm. +- **Dtoa Shortest**: 100% for Schubfach and Grisu3; Grisu2 and Grisu must not drop. +- **Atod Exact**: 100%. + +Any drop fails. Report both tables and, for a failure, a failing input with the expected and actual output. + +The suite only calls `dconv::atod (first, last, value)` (`StringProbe`): `StreamProbe` and the `std::istream` overloads are covered only by the gtests; say so in the report. diff --git a/.agents/skills/coverage/SKILL.md b/.agents/skills/coverage/SKILL.md new file mode 100644 index 0000000..a0f443d --- /dev/null +++ b/.agents/skills/coverage/SKILL.md @@ -0,0 +1,24 @@ +--- +name: coverage +description: Check dconv coverage is not degraded versus main (gcc and clang). Use at closing. +--- + +# Coverage + +Coverage must not drop on **gcc and clang**. Use the `debug` presets (only ones with coverage) and CI's `gcovr` options: + +```bash +C=gcc # repeat with C=clang +GCOV=gcov; [ "$C" = clang ] && GCOV='llvm-cov gcov' +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 +``` + +Baseline: same steps on `main` in a worktree (`git worktree add --detach /base main`) 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` (`pipx install gcovr`) and `llvm-cov` matching clang. If missing, report coverage as **not verified**, never "not degraded". diff --git a/.agents/skills/create-pr/SKILL.md b/.agents/skills/create-pr/SKILL.md new file mode 100644 index 0000000..5f3cb4f --- /dev/null +++ b/.agents/skills/create-pr/SKILL.md @@ -0,0 +1,22 @@ +--- +name: create-pr +description: Branch, commit, push and open a PR with the dconv conventions. Use when asked to commit, push or open a PR. +--- + +# Branch, commit, push + +**Ask the user before each of: creating the branch (propose the name), committing (show files and message), pushing (branch and remote), opening the PR (title and base).** One approval covers one action. Never merge, force-push or push to `main`. + +1. **Inspect**: `git status`, `git diff`, current branch. Stage nothing unrelated, nothing under `build/`. +2. **Validate**: the "Definition of done" in `AGENTS.md` is met and non-trivial changes were reviewed. Do not commit if an item fails; say what was not verified when asking. +3. **Branch**: on `main`, `git switch -c `. Stay on a suitable branch; never commit on `main`. + - `feature/`: additions (algorithm, API, option, tool). + - `bugfix/`: real bug, root cause fixed, regression test added. + - `hotfix/`: small quick fix, minimal diff. + + Ask once if bugfix vs hotfix is unclear. +4. **Version**: bump `VERSION` when the library or tools change (not for CI, docs or agent config). Patch for bugfix/hotfix, minor for a feature or an API change, 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`. +8. **Report** branch, commit, presets run and the link; do not claim CI is green. diff --git a/.agents/skills/gen-tables/SKILL.md b/.agents/skills/gen-tables/SKILL.md new file mode 100644 index 0000000..f4967c4 --- /dev/null +++ b/.agents/skills/gen-tables/SKILL.md @@ -0,0 +1,30 @@ +--- +name: gen-tables +description: Regenerate a power-of-ten table with tools/*powgen and keep lookups in sync. Use when changing a table, its range or a generator. +--- + +# Generate power tables + +Never hand-edit table entries; every change goes through the generator. + +| Header (`dconv/include/dconv/`) | Generator | Default range | Lookup | +|---|---|---|---| +| `grisupow.hpp` (`DiyFp[]`) | `grisupowgen` | `-343..343` | `mk + 343` in `grisu.hpp` | +| `eisellemirepow.hpp` (`Power[]`) | `eisellemirepowgen` | `-342..308` | `exponent + 342` in `eisellemire.hpp` | +| `schubfachpow.hpp` (`alignas (64) Power[]`) | `schubfachpowgen` | `-292..324` | `292 - k` in `schubfach.hpp` | + +1. **Build** the tools with any preset (skill `compile`); binaries are in `build/<compiler>/<kind>/tools/<gen>/<gen>`. +2. **Check reproducibility first**: run the generator with the default range and compare its entries with the current header; they must match before any change. + + ```bash + entries () { grep -oE '\{0x[0-9a-f]+, *-?(0x)?[0-9a-f]+\}' "$1" | tr -d ' '; } + build/<compiler>/<kind>/tools/<gen>/<gen> > <scratchpad>/<gen>.hpp + diff <(entries <scratchpad>/<gen>.hpp) <(entries dconv/include/dconv/<table>.hpp) + ``` + +3. **Generate** with the new range (`-l <lower> -u <upper>`) or the modified generator. +4. **Paste** only the entries into the existing table; keep the header's guard, includes, namespace, declaration (name, element type, `alignas`) and Doxygen. Let `clang-format` lay out the lines. +5. **Sync** the lookup offset, any range assertion (`assert` in `grisu.hpp`) and the exponent bounds checks in the algorithm with the new range; update the generator README if its defaults changed. +6. **Verify**: re-run the entries diff against the new output, then the closing checks of `AGENTS.md` "Definition of done" (`conformance` and `performance` included). + +Report the range, entry count before/after, and the test and conformance results. diff --git a/.agents/skills/performance/SKILL.md b/.agents/skills/performance/SKILL.md new file mode 100644 index 0000000..2322f01 --- /dev/null +++ b/.agents/skills/performance/SKILL.md @@ -0,0 +1,34 @@ +--- +name: performance +description: Check dtoa/atod speed versus main with dconv-benchmark on a real machine. Use at closing when conversion code changed. +--- + +# Performance + +Required at closing when conversion code changes (`dconv/include/dconv/`). Done means no slowdown of the dconv entries versus `main` beyond the run-to-run spread. If no suitable machine is available, report it as **not verified** (WSL2 numbers are indicative only). + +## 1. Ask first + +Ask the user: **is a real machine available (ideally x86_64 and aarch64)?** Ask where `dconv-benchmark` is cloned; never clone it into the dconv tree. + +## 2. Performance mode + +The manual runs below bypass `run_bench.sh`'s checks: verify yourself that `cpu<N>/cpufreq/scaling_governor` is `performance` and boost is off (`cpufreq/boost` = 0, or `intel_pstate/no_turbo` = 1). Read and note the current values, then: + +```bash +echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor +echo 0 | sudo tee /sys/devices/system/cpu/cpufreq/boost # AMD / acpi-cpufreq +echo 1 | sudo tee /sys/devices/system/cpu/intel_pstate/no_turbo # Intel intel_pstate +``` + +Use `sudo` only when justified; otherwise give the commands with the `! ` prefix. No `ALLOW_NOISY=1` unless the user accepts noisy numbers. Remind the user of the original values at the end. + +## 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). + +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. diff --git a/.agents/skills/release/SKILL.md b/.agents/skills/release/SKILL.md new file mode 100644 index 0000000..d3d4095 --- /dev/null +++ b/.agents/skills/release/SKILL.md @@ -0,0 +1,22 @@ +--- +name: release +description: Tag and publish a dconv release from main. Use when asked to release or tag a version. +--- + +# Release + +**Ask the user before creating the tag and again before pushing it.** Never move, delete or re-push an existing tag; never push anything else. + +1. **Check**: + - On `main`, clean tree, up to date: `git fetch origin --tags && git status` and `git rev-parse HEAD origin/main` equal. + - `VERSION` was bumped by a merged PR (skill `create-pr`), is greater than the latest tag (`git tag -l --sort=-v:refname | head -1`) and `v$(cat VERSION)` does not exist locally or on `origin`. + - `PROJECT_NUMBER` in `doxyfile` = `<major>.<minor>` of `VERSION`. + - CI (`cd` workflow) is green on this commit: ask the user, or check with `gh run list --workflow cd.yml --commit $(git rev-parse HEAD)` if available. + + If a check fails, stop and say why. +2. **Tag**: lightweight tag on `HEAD` (= `origin/main`, the squash-merged PR commit), like the existing ones: `git tag v$(cat VERSION)`. +3. **Push**: `git push origin v$(cat VERSION)`. `release.yml` checks the tag against `VERSION`, builds (scan, asan, pack) and publishes the GitHub release with the `.deb` and generated notes. +4. **Report** the tag, commit and `https://github.com/joinframework/dconv/actions/workflows/release.yml`; do not claim the release is published until the user confirms the workflow passed. +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). diff --git a/.agents/skills/test/SKILL.md b/.agents/skills/test/SKILL.md new file mode 100644 index 0000000..f579140 --- /dev/null +++ b/.agents/skills/test/SKILL.md @@ -0,0 +1,21 @@ +--- +name: test +description: Run dconv tests (ctest or one gtest), including sanitizers. Use to validate a change or reproduce a failure. +--- + +# Test + +Build first with skill `compile`. Dev loop vs closing: see `AGENTS.md` "Definition of done". + +```bash +ctest --preset <preset> --no-tests=error [-R '^<Suite>\.'] # Suite = first TEST argument, e.g. StreamProbe, schubfach +build/<compiler>/<kind>/dconv/tests/<name>.gtest --gtest_filter='suite.test' +``` + +- `debug` and `release` on gcc and clang, like CI. `release` (`-O3`) can expose what `debug` hides. +- `asan` (ASan + UBSan, `detect_leaks=1`) for buffer, range and UB issues; the `asan` test presets set `ASAN_OPTIONS`/`UBSAN_OPTIONS` (export the same values when running a binary directly). +- A failing conversion: print the input and the bits of the double (`%a`) before changing code. + +## Report + +Presets run and pass/fail counts, with the output of any failure. Never claim success you did not observe. diff --git a/.claude/agents/explorer.md b/.claude/agents/explorer.md new file mode 100644 index 0000000..62d9e5f --- /dev/null +++ b/.claude/agents/explorer.md @@ -0,0 +1,28 @@ +--- +name: explorer +description: "Read-only: map where a new dconv feature fits before coding. Use before implementing a feature." +tools: Read, Grep, Glob, Bash +--- + +You explore **dconv** before a feature is developed. Strictly read-only: never edit, write, commit or push; Bash for inspection only (`git log`, `git show`, `grep`, `ls`), never builds. Read `AGENTS.md` and the matching `.claude/rules/` first. + +## Method + +1. Restate the feature in one sentence; say whether it is dtoa, atod, probes, formatting or tooling. +2. Find the closest analogues (e.g. `grisu2.hpp` for a new dtoa algorithm, `StreamProbe` for a new input source) and read their API, namespaces and shared helpers (`format.hpp`, `digits.hpp`, `power.hpp`). +3. Locate everything touched: headers, `PUBLIC_HEADERS`, tests, power tables and generators, README tables, `dtoa.hpp` default. +4. Check history (`git log`, `git show`) on the analogues for earlier decisions. +5. List the tests needed (edge doubles, roundtrip) and the closing checks (coverage, skills `conformance` and `performance`). + +Stay narrow. Ground every claim in a `path:line`; say "not verified" otherwise. + +## Output (under about 50 lines) + +- **Fit**: files to add or change. +- **Analogues**: what to imitate, with `path:line`. +- **Design options**: 2-3 at most, with a recommendation (speed, correctness, API shape). +- **Impact**: tests, README, version bump, benchmark adapters. +- **Risks**: rounding, buffer size, aarch64, `errno`, API breakage. +- **Open questions** that change the design. + +No diff, no implementation. diff --git a/.claude/agents/reviewer.md b/.claude/agents/reviewer.md new file mode 100644 index 0000000..226bda1 --- /dev/null +++ b/.claude/agents/reviewer.md @@ -0,0 +1,27 @@ +--- +name: reviewer +description: "Read-only: review a dconv change for correctness, memory, performance and conventions. Use before a PR." +tools: Read, Grep, Glob, Bash +--- + +You review C++20 changes in **dconv**. Strictly read-only: never edit, write, commit, push or comment on a PR. Bash for inspection (`git diff`, `git log`, `grep`) and, only if asked, building or testing in a scratch tree. + +## Scope + +Default: `git diff HEAD` (staged + unstaged) plus `git status --short --untracked-files=all`, reading new files in full; otherwise `git diff main...<ref>`. Read the surrounding code and tests, `AGENTS.md` and the matching `.claude/rules/`. + +## Check, in priority order + +1. **Correctness**: shortest output and round trip (dtoa), correct rounding incl. halfway, subnormals, `DBL_MAX`, > 19 digits (atod), sign of zero, `inf`/`nan`, overflow, table offsets (check that skill `conformance` was reported; run it only if asked). +2. **Memory**: buffer overruns (dtoa 25/26 bytes), reading past `last`, dangling pointers into temporaries, stream position after failure. +3. **Portability**: aarch64, `__uint128_t` only behind `#if defined(__SIZEOF_INT128__)` with a portable `#else` (check it by reading), endianness, `char` signedness, `errno` left unchanged. +4. **Performance**: allocations, locale, extra branches or divisions on the hot path, unmeasured claims (check that skill `performance` was reported; run it only if asked). +5. **Conventions**: `.claude/rules/*`, Doxygen, `PUBLIC_HEADERS`, README, `VERSION`. +6. **Tests**: one test per function, edge doubles, registered in CMake, no weakened assertions. +7. **Agent config** (`AGENTS.md`, `.agents/`, `.claude/`, `CLAUDE.md`): `AGENTS.md` and `.agents/` stay tool-agnostic (no dependency on `.claude/` files or Claude-only features); Claude-specific content stays in `.claude/` and `CLAUDE.md`; `CLAUDE.md` only imports `AGENTS.md`; every file stays short. + +Skip what `clang-format` fixes and speculative refactors. + +## Output + +Findings by severity: `path:line`, one-sentence defect, concrete failing input, proposed fix as a small diff (not applied). Mark **confirmed** or **plausible**; drop what you cannot ground. If nothing survives, say "No findings" and what you checked. End with a one-line verdict. diff --git a/.claude/hooks/format.sh b/.claude/hooks/format.sh new file mode 100755 index 0000000..212ce6b --- /dev/null +++ b/.claude/hooks/format.sh @@ -0,0 +1,15 @@ +#!/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 +} +command -v jq >/dev/null || missing jq +file=$(jq -r '.tool_input.file_path // empty') +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 +exit 0 diff --git a/.claude/rules/cpp-style.md b/.claude/rules/cpp-style.md new file mode 100644 index 0000000..140435c --- /dev/null +++ b/.claude/rules/cpp-style.md @@ -0,0 +1,32 @@ +--- +paths: + - "dconv/**/*.hpp" + - "dconv/**/*.cpp" +--- + +# C++ style + +`.clang-format` is enforced (hook, or `clang-format` 19); 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. +- Header: MIT license, `#ifndef __DCONV_<NAME>_HPP__` (no `#pragma once`), content in indented `namespace dconv`; internals in `details` or the algorithm namespace (`schubfach`, `grisu2`...). New public headers go in `PUBLIC_HEADERS` (`dconv/CMakeLists.txt`). +- Include groups: `// dconv.`, `// libraries.` (tests), `// C++.`, `// C.`; `<dconv/...>` only. + +## Naming +- `PascalCase` types, enum values and template parameters (`Probe`, `Rewind`); `camelCase` functions; class members `_camelCase` (plain aggregates such as `details::Power` keep `hi`/`lo`); macros `DCONV_UPPER`; fixed-width types unprefixed (`uint64_t`). Std-mandated names keep their spelling (`make_error_code`). + +## Classes +- Model: `StringProbe`/`StreamProbe`. Order: `public` then `private`; constructors, special members, destructor, API, then `static constexpr` members, then default-initialised data members. `DiyFp` (public data read by Grisu) and `ErrorCategory` are older exceptions: do not imitate them, do not change them unasked. +- New classes declare every special member (`= default` or `= delete`, e.g. `StreamProbe` non-copyable); `explicit` single-argument constructors; `override` on virtuals; constructor initializers one per line, leading comma. +- Overloads that would return a pointer into a temporary are `= delete` (`atod (std::string&&, ...)`). Compound operators are members, binary operators free functions after the class. + +## Code +- Free and out-of-class header functions `inline` (`constexpr`, in-class and template definitions may omit it); `noexcept` when it cannot throw; `constexpr` where possible. Named casts only; `memcpy` for type punning. +- Errors: return `nullptr` / `false` / `-1` and `Errc` (`error.hpp`); no exceptions. Never change `errno` (save and restore around `strtod`). +- Performance first: no allocation, no locale, bounded loops, tables computed at build time; `DCONV_LIKELY`/`DCONV_UNLIKELY` on branches. Works on aarch64; `__uint128_t` only behind `#if defined(__SIZEOF_INT128__)` with a portable `#else` (never compiled by CI: review it by reading). + +## Doxygen +- New header declarations (tests follow `tests.md`): `/**`, ` * @brief ...` (lowercase), then `@param`/`@return` lines, ` */`; never `///` for functions or types; state buffer size requirements. +- Wording: constructors `default constructor.` (the probes, also with arguments) or `create <what> using <args>.` (`DiyFp`); `copy constructor.`, `copy assignment.`, `move constructor.`, `move assignment.`, `destroy instance.`; `@param other object to copy.` / `object to move.`; `@return a reference of the current object.`. Members and constants: `/// one line.`; enum values: trailing `/**< ... */`. +- No comments inside function bodies except `LCOV_EXCL_*` markers. diff --git a/.claude/rules/power-tables.md b/.claude/rules/power-tables.md new file mode 100644 index 0000000..ce0adeb --- /dev/null +++ b/.claude/rules/power-tables.md @@ -0,0 +1,15 @@ +--- +paths: + - "dconv/include/dconv/*pow.hpp" + - "dconv/include/dconv/grisu.hpp" + - "dconv/include/dconv/eisellemire.hpp" + - "dconv/include/dconv/schubfach.hpp" + - "tools/**" +--- + +# Power tables + +- `grisupow.hpp`, `schubfachpow.hpp`, `eisellemirepow.hpp` are generated by `tools/{grisupowgen,schubfachpowgen,eisellemirepowgen}` (ranges `-l`/`-u` in each tool README). Never hand-edit entries. +- The tools output a standalone header (`powers[]`, `MIN_EXPONENT`): paste the entries into the existing `dconv::details` table, keep its declaration as is (name, element type, `alignas` where present). +- Lookup offsets must match the range: `mk + 343` (`grisu.hpp`), `exponent + 342` (`eisellemire.hpp`), `292 - k` (`schubfach.hpp`). +- Tool changes: update its README. diff --git a/.claude/rules/tests.md b/.claude/rules/tests.md new file mode 100644 index 0000000..fda5337 --- /dev/null +++ b/.claude/rules/tests.md @@ -0,0 +1,19 @@ +--- +paths: + - "**/tests/**" +--- + +# Tests + +- GoogleTest. **One file per header**, or per class when a header holds several (`probe.hpp` -> `string_probe_test.cpp`, `stream_probe_test.cpp`). Suite named after the namespace, header or class (`TEST (schubfach, floorLog2Pow10)`, `TEST (StreamProbe, peek)`, `TEST (ErrorCategory, name)`). **One `TEST` per function**, all its paths (success, errors, boundaries, overloads) inside; constructors in `TEST (<Class>, create)`, assignments in `TEST (<Class>, assign)`. Cross-cutting tests (`roundtrip_test.cpp`) are the exception. +- Each test has a three-line `/**` / ` * @brief <function> test.` / ` */` block and the file ends with the `main` of its neighbours. `ASSERT_*` when later lines depend on it, `EXPECT_*` otherwise. +- Register in `dconv/tests/CMakeLists.txt`: + +```cmake +add_executable(<name>.gtest <name>_test.cpp) +target_link_libraries(<name>.gtest ${PROJECT_NAME} GTest::gtest_main pthread) +gtest_discover_tests(<name>.gtest) +``` + +- Cover edge doubles: `±0`, subnormals (`5e-324`), `DBL_MIN`/`DBL_MAX`, halfway cases, > 19-digit mantissas, `inf`/`nan`, invalid input. Output changes need a case in `roundtrip_test.cpp`. +- Never weaken or delete an assertion to make a test pass. diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..86d1cef --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,37 @@ +{ + "attribution": { + "commit": "", + "pr": "" + }, + "permissions": { + "ask": [ + "Bash(git switch -c:*)", + "Bash(git checkout -b:*)", + "Bash(git commit:*)", + "Bash(git push:*)" + ], + "deny": [ + "Bash(git push --force:*)", + "Bash(git push --force-with-lease:*)", + "Bash(git push -f:*)", + "Bash(git push origin main:*)", + "Bash(git push -u origin main:*)", + "Bash(git push origin HEAD:main:*)", + "Bash(git merge:*)", + "Bash(gh pr merge:*)" + ] + }, + "hooks": { + "PostToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/format.sh" + } + ] + } + ] + } +} diff --git a/.claude/skills/add-gtest b/.claude/skills/add-gtest new file mode 120000 index 0000000..821cbc4 --- /dev/null +++ b/.claude/skills/add-gtest @@ -0,0 +1 @@ +../../.agents/skills/add-gtest \ No newline at end of file diff --git a/.claude/skills/compile b/.claude/skills/compile new file mode 120000 index 0000000..0f8e80b --- /dev/null +++ b/.claude/skills/compile @@ -0,0 +1 @@ +../../.agents/skills/compile \ No newline at end of file diff --git a/.claude/skills/conformance b/.claude/skills/conformance new file mode 120000 index 0000000..b41ccaa --- /dev/null +++ b/.claude/skills/conformance @@ -0,0 +1 @@ +../../.agents/skills/conformance \ No newline at end of file diff --git a/.claude/skills/coverage b/.claude/skills/coverage new file mode 120000 index 0000000..a6c2678 --- /dev/null +++ b/.claude/skills/coverage @@ -0,0 +1 @@ +../../.agents/skills/coverage \ No newline at end of file diff --git a/.claude/skills/create-pr b/.claude/skills/create-pr new file mode 120000 index 0000000..d5b42b7 --- /dev/null +++ b/.claude/skills/create-pr @@ -0,0 +1 @@ +../../.agents/skills/create-pr \ No newline at end of file diff --git a/.claude/skills/gen-tables b/.claude/skills/gen-tables new file mode 120000 index 0000000..a283367 --- /dev/null +++ b/.claude/skills/gen-tables @@ -0,0 +1 @@ +../../.agents/skills/gen-tables \ No newline at end of file diff --git a/.claude/skills/performance b/.claude/skills/performance new file mode 120000 index 0000000..e10ee71 --- /dev/null +++ b/.claude/skills/performance @@ -0,0 +1 @@ +../../.agents/skills/performance \ No newline at end of file diff --git a/.claude/skills/release b/.claude/skills/release new file mode 120000 index 0000000..14f8a38 --- /dev/null +++ b/.claude/skills/release @@ -0,0 +1 @@ +../../.agents/skills/release \ No newline at end of file diff --git a/.claude/skills/test b/.claude/skills/test new file mode 120000 index 0000000..20211d1 --- /dev/null +++ b/.claude/skills/test @@ -0,0 +1 @@ +../../.agents/skills/test \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..5a81ecb --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,37 @@ +# AGENTS.md + +**dconv**: header-only C++20 double ⇄ string library (`dconv/include/dconv/`), used by join. Tests in `dconv/tests/`, table generators in `tools/`. + +## Build and test + +Presets `gcc|clang` x `debug|release|asan` (debug = coverage), trees in `build/<compiler>/<kind>`. `-Werror` is on. + +```bash +cmake --preset gcc-debug && cmake --build --preset gcc-debug && ctest --preset gcc-debug +``` + +## Architecture + +- **dtoa**: `dconv::dtoa` -> `schubfach::dtoa`. Each algorithm (`schubfach`, `grisu`, `grisu2`, `grisu3`) has `generate (buffer, length, k, value)`; the Grisu family renders it through the `grisu.hpp` driver and `details::prettify`, while `schubfach::dtoa` calls `shortest` then `details::format`; `grisu3` falls back to `std::to_chars`. +- **atod**: all overloads go through `details::atod<Probe>`. `StringProbe` seeks back to re-parse > 19 digits; `StreamProbe` never rewinds, so they go to `atodLong` (Eisel-Lemire bracketing, `strtod` only on disagreement). `Errc::NotDouble` reports 64-bit integers. +- **Power tables** (`*pow.hpp`): generated by `tools/*powgen`. + +## 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. + +Changes to `AGENTS.md` and `.agents/` keep them tool-agnostic and short; `.claude/` is Claude Code only. + +## Definition of done + +- **Dev loop**: `gcc-debug`, the relevant test only. +- **Closing** (before asking to commit), gcc and clang: scope limited to the request; tests added; `debug`, `release`, `asan` pass; `coverage` not degraded; if conversion code changed, `conformance` and `performance` not degraded; `README.md` updated if the API or behaviour changes, else say none needed. + +Report what ran; never call a partial pass done. A missing tool or wrong version: ask before installing (`sudo` if justified), else give the command with the `! ` prefix, and report the item **not verified**. + +## Working agreements + +- English for code, comments, commits, PRs, docs and branches. +- Brief answers. Change only what was requested; in reviews and cleanups, one diff at a time, wait for approval. +- Ask before each branch, commit, push and PR (skill `create-pr`). Never force-push, push to `main` or merge. +- Never edit `build/` or generated `version.hpp` files. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md From 7c87314cb39d6aaefcdceabf28663c696617b868 Mon Sep 17 00:00:00 2001 From: mrabine <rabine.mathieu@gmail.com> Date: Wed, 11 Nov 2026 18:24:01 +0100 Subject: [PATCH 2/5] Align agent config with user preferences --- .agents/skills/coverage/SKILL.md | 10 +++++++++- .agents/skills/create-pr/SKILL.md | 2 +- .agents/skills/gen-tables/SKILL.md | 4 ++-- .claude/rules/cpp-style.md | 6 +++--- .claude/settings.json | 16 +++++++++++++--- AGENTS.md | 10 ++++++---- VERSION | 2 +- 7 files changed, 35 insertions(+), 15 deletions(-) diff --git a/.agents/skills/coverage/SKILL.md b/.agents/skills/coverage/SKILL.md index a0f443d..9b78745 100644 --- a/.agents/skills/coverage/SKILL.md +++ b/.agents/skills/coverage/SKILL.md @@ -21,4 +21,12 @@ Baseline: same steps on `main` in a worktree (`git worktree add --detach <scratc 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` (`pipx install gcovr`) and `llvm-cov` matching clang. If missing, report coverage as **not verified**, never "not degraded". +Needs `gcovr` and `llvm-cov` matching clang. Without them locally, run the steps above 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 ./ <scratchpad>/head/ +docker run --rm --user "$(id -u):$(id -g)" -v <scratchpad>:<scratchpad> -w <scratchpad>/head \ + joinframework/join-ci:latest bash -c '<steps above>' +``` + +Same for the baseline with `-w <scratchpad>/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 5f3cb4f..cb3e46b 100644 --- a/.agents/skills/create-pr/SKILL.md +++ b/.agents/skills/create-pr/SKILL.md @@ -8,7 +8,7 @@ description: Branch, commit, push and open a PR with the dconv conventions. Use **Ask the user before each of: creating the branch (propose the name), committing (show files and message), pushing (branch and remote), opening the PR (title and base).** One approval covers one action. Never merge, force-push or push to `main`. 1. **Inspect**: `git status`, `git diff`, current branch. Stage nothing unrelated, nothing under `build/`. -2. **Validate**: the "Definition of done" in `AGENTS.md` is met and non-trivial changes were reviewed. Do not commit if an item fails; say what was not verified when asking. +2. **Validate**: the dev loop of `AGENTS.md` "Definition of done" passed; the closing checks run only when the user says the change is ready to merge. Say what was not verified when asking. 3. **Branch**: on `main`, `git switch -c <prefix><kebab-name>`. Stay on a suitable branch; never commit on `main`. - `feature/`: additions (algorithm, API, option, tool). - `bugfix/`: real bug, root cause fixed, regression test added. diff --git a/.agents/skills/gen-tables/SKILL.md b/.agents/skills/gen-tables/SKILL.md index f4967c4..65c7176 100644 --- a/.agents/skills/gen-tables/SKILL.md +++ b/.agents/skills/gen-tables/SKILL.md @@ -25,6 +25,6 @@ Never hand-edit table entries; every change goes through the generator. 3. **Generate** with the new range (`-l <lower> -u <upper>`) or the modified generator. 4. **Paste** only the entries into the existing table; keep the header's guard, includes, namespace, declaration (name, element type, `alignas`) and Doxygen. Let `clang-format` lay out the lines. 5. **Sync** the lookup offset, any range assertion (`assert` in `grisu.hpp`) and the exponent bounds checks in the algorithm with the new range; update the generator README if its defaults changed. -6. **Verify**: re-run the entries diff against the new output, then the closing checks of `AGENTS.md` "Definition of done" (`conformance` and `performance` included). +6. **Verify**: re-run the entries diff against the new output and the dev loop of `AGENTS.md` "Definition of done"; `conformance` and `performance` belong to the closing checks, run when the user says the change is ready to merge. -Report the range, entry count before/after, and the test and conformance results. +Report the range, entry count before/after, and the test results. diff --git a/.claude/rules/cpp-style.md b/.claude/rules/cpp-style.md index 140435c..01dacf1 100644 --- a/.claude/rules/cpp-style.md +++ b/.claude/rules/cpp-style.md @@ -11,7 +11,7 @@ paths: ## 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. - Header: MIT license, `#ifndef __DCONV_<NAME>_HPP__` (no `#pragma once`), content in indented `namespace dconv`; internals in `details` or the algorithm namespace (`schubfach`, `grisu2`...). New public headers go in `PUBLIC_HEADERS` (`dconv/CMakeLists.txt`). -- Include groups: `// dconv.`, `// libraries.` (tests), `// C++.`, `// C.`; `<dconv/...>` only. +- Include groups: `// dconv.`, `// libraries.` (tests), `// C++.`, `// C.`, each sorted by line length, longest first; `<dconv/...>` only. ## Naming - `PascalCase` types, enum values and template parameters (`Probe`, `Rewind`); `camelCase` functions; class members `_camelCase` (plain aggregates such as `details::Power` keep `hi`/`lo`); macros `DCONV_UPPER`; fixed-width types unprefixed (`uint64_t`). Std-mandated names keep their spelling (`make_error_code`). @@ -24,9 +24,9 @@ paths: ## Code - Free and out-of-class header functions `inline` (`constexpr`, in-class and template definitions may omit it); `noexcept` when it cannot throw; `constexpr` where possible. Named casts only; `memcpy` for type punning. - Errors: return `nullptr` / `false` / `-1` and `Errc` (`error.hpp`); no exceptions. Never change `errno` (save and restore around `strtod`). -- Performance first: no allocation, no locale, bounded loops, tables computed at build time; `DCONV_LIKELY`/`DCONV_UNLIKELY` on branches. Works on aarch64; `__uint128_t` only behind `#if defined(__SIZEOF_INT128__)` with a portable `#else` (never compiled by CI: review it by reading). +- Hot path constraints: no allocation, no locale, bounded loops, tables computed at build time; `DCONV_LIKELY`/`DCONV_UNLIKELY` on branches. Works on aarch64; `__uint128_t` only behind `#if defined(__SIZEOF_INT128__)` with a portable `#else` (never compiled by CI: review it by reading). ## Doxygen -- New header declarations (tests follow `tests.md`): `/**`, ` * @brief ...` (lowercase), then `@param`/`@return` lines, ` */`; never `///` for functions or types; state buffer size requirements. +- New header declarations (tests follow `tests.md`): `/**`, ` * @brief ...` (lowercase), then `@param`/`@return`/`@throw` lines, ` */`; only these four tags (no `@note`, `@tparam`); never `///` for functions or types. One short line per tag, a noun phrase ending with a period; what would go in a note belongs in the `@brief` or `@return`. State buffer size requirements in the `@param` (`(must point to at least 25 writable bytes)`, `dtoa.hpp:36`). - Wording: constructors `default constructor.` (the probes, also with arguments) or `create <what> using <args>.` (`DiyFp`); `copy constructor.`, `copy assignment.`, `move constructor.`, `move assignment.`, `destroy instance.`; `@param other object to copy.` / `object to move.`; `@return a reference of the current object.`. Members and constants: `/// one line.`; enum values: trailing `/**< ... */`. - No comments inside function bodies except `LCOV_EXCL_*` markers. diff --git a/.claude/settings.json b/.claude/settings.json index 86d1cef..31e9dba 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -5,10 +5,20 @@ }, "permissions": { "ask": [ - "Bash(git switch -c:*)", - "Bash(git checkout -b:*)", + "Bash(git add:*)", + "Bash(git switch:*)", + "Bash(git checkout:*)", + "Bash(git restore:*)", + "Bash(git reset:*)", + "Bash(git stash:*)", + "Bash(git rebase:*)", + "Bash(git cherry-pick:*)", + "Bash(git branch:*)", + "Bash(git tag:*)", + "Bash(git worktree:*)", "Bash(git commit:*)", - "Bash(git push:*)" + "Bash(git push:*)", + "Bash(gh pr create:*)" ], "deny": [ "Bash(git push --force:*)", diff --git a/AGENTS.md b/AGENTS.md index 5a81ecb..6c38da4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,14 +24,16 @@ Changes to `AGENTS.md` and `.agents/` keep them tool-agnostic and short; `.claud ## Definition of done -- **Dev loop**: `gcc-debug`, the relevant test only. -- **Closing** (before asking to commit), gcc and clang: scope limited to the request; tests added; `debug`, `release`, `asan` pass; `coverage` not degraded; if conversion code changed, `conformance` and `performance` not degraded; `README.md` updated if the API or behaviour changes, else say none needed. +- **Dev loop**: `gcc-debug`, the relevant test only, once the batch is reviewed (not after each edit). Renames: diff and `clang-format` only. Design still moving: code and benchmarks only. +- **Closing** (once, when the user says ready to merge), gcc and clang: scope limited to the request; tests added; `debug`, `release`, `asan` pass; `coverage` not degraded; if conversion code changed, `conformance` and `performance` not degraded; `README.md` updated if the API or behaviour changes, else say none needed. Report what ran; never call a partial pass done. A missing tool or wrong version: ask before installing (`sudo` if justified), else give the command with the `! ` prefix, and report the item **not verified**. ## Working agreements - English for code, comments, commits, PRs, docs and branches. -- Brief answers. Change only what was requested; in reviews and cleanups, one diff at a time, wait for approval. -- Ask before each branch, commit, push and PR (skill `create-pr`). Never force-push, push to `main` or merge. +- Brief answers. Change only what was requested; extras as separate proposals. +- Plain code first, measured; tricks, fast paths or new helpers only as a measured, proposed option. +- Show one diff at a time; edit only after an explicit yes covering that diff ("ok" after an either/or question is not a choice). Reading, building, measuring and scratchpad work need no approval. +- Ask before any state-changing git command and each PR (skill `create-pr`); read-only ones are fine. Never force-push, push to `main` or merge. - Never edit `build/` or generated `version.hpp` files. diff --git a/VERSION b/VERSION index 6085e94..23aa839 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.1 +1.2.2 From bc018fbdaad57d8c5fd45f10e0872c7d7a101f26 Mon Sep 17 00:00:00 2001 From: mrabine <rabine.mathieu@gmail.com> Date: Wed, 11 Nov 2026 18:38:35 +0100 Subject: [PATCH 3/5] Allow read-only commands without prompt --- .claude/settings.json | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.claude/settings.json b/.claude/settings.json index 31e9dba..70a99a7 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -4,6 +4,23 @@ "pr": "" }, "permissions": { + "allow": [ + "Bash(git status:*)", + "Bash(git log:*)", + "Bash(git diff:*)", + "Bash(git show:*)", + "Bash(git branch:*)", + "Bash(git rev-parse:*)", + "Bash(git ls-files:*)", + "Bash(git grep:*)", + "Bash(git blame:*)", + "Bash(cat:*)", + "Bash(head:*)", + "Bash(tail:*)", + "Bash(ls:*)", + "Bash(grep:*)", + "Bash(wc:*)" + ], "ask": [ "Bash(git add:*)", "Bash(git switch:*)", @@ -13,7 +30,10 @@ "Bash(git stash:*)", "Bash(git rebase:*)", "Bash(git cherry-pick:*)", - "Bash(git branch:*)", + "Bash(git branch -d:*)", + "Bash(git branch -D:*)", + "Bash(git branch -m:*)", + "Bash(git branch -M:*)", "Bash(git tag:*)", "Bash(git worktree:*)", "Bash(git commit:*)", From 21c83d6a9cb383ec5aded270cb3bf25472bf2ef6 Mon Sep 17 00:00:00 2001 From: mrabine <rabine.mathieu@gmail.com> Date: Wed, 11 Nov 2026 18:46:03 +0100 Subject: [PATCH 4/5] Address agent config review --- .agents/skills/conformance/SKILL.md | 2 +- .agents/skills/coverage/SKILL.md | 6 ++--- .claude/hooks/format.sh | 4 +++- .claude/rules/cpp-style.md | 2 ++ .claude/settings.json | 36 ----------------------------- AGENTS.md | 2 +- 6 files changed, 10 insertions(+), 42 deletions(-) diff --git a/.agents/skills/conformance/SKILL.md b/.agents/skills/conformance/SKILL.md index b410450..00cac18 100644 --- a/.agents/skills/conformance/SKILL.md +++ b/.agents/skills/conformance/SKILL.md @@ -10,7 +10,7 @@ Required at closing when conversion code changes (`dconv/include/dconv/`). No ti Ask where `dconv-benchmark` is cloned (`https://github.com/joinframework/dconv-benchmark`); never clone it into the dconv tree. It pins dconv to a release tag, so point both builds at local checkouts, `main` in a worktree: ```bash -git -C <dconv> worktree add --detach <scratchpad>/base main +git -C <dconv> fetch origin main && git -C <dconv> worktree add --detach <scratchpad>/base origin/main cmake -S <bench> -B <bench>/build/main -DCMAKE_BUILD_TYPE=Release -DFETCHCONTENT_SOURCE_DIR_DCONV=<scratchpad>/base cmake -S <bench> -B <bench>/build/branch -DCMAKE_BUILD_TYPE=Release -DFETCHCONTENT_SOURCE_DIR_DCONV=<dconv> cmake --build <build> --target conformance diff --git a/.agents/skills/coverage/SKILL.md b/.agents/skills/coverage/SKILL.md index 9b78745..ed20a68 100644 --- a/.agents/skills/coverage/SKILL.md +++ b/.agents/skills/coverage/SKILL.md @@ -17,16 +17,16 @@ gcovr --root . --gcov-executable "$GCOV" --exclude-pattern-prefix LCOV --exclude --exclude '.*/tests/.*' --exclude 'tools/.*' --json-summary <scratchpad>/$C-after.json --print-summary build/$C/debug ``` -Baseline: same steps on `main` in a worktree (`git worktree add --detach <scratchpad>/base 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 <scratchpad>/base origin/main`) 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, run the steps above 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 to `<scratchpad>/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 ./ <scratchpad>/head/ docker run --rm --user "$(id -u):$(id -g)" -v <scratchpad>:<scratchpad> -w <scratchpad>/head \ - joinframework/join-ci:latest bash -c '<steps above>' + joinframework/join-ci:latest bash <scratchpad>/coverage.sh ``` Same for the baseline with `-w <scratchpad>/base`. If neither works, report coverage as **not verified**, never "not degraded". diff --git a/.claude/hooks/format.sh b/.claude/hooks/format.sh index 212ce6b..72bb680 100755 --- a/.claude/hooks/format.sh +++ b/.claude/hooks/format.sh @@ -3,8 +3,10 @@ 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') +file=$(jq -r '.tool_input.file_path // empty' <<<"$input") case "$file" in *.hpp|*.cpp) command -v clang-format >/dev/null || missing clang-format diff --git a/.claude/rules/cpp-style.md b/.claude/rules/cpp-style.md index 01dacf1..d350e00 100644 --- a/.claude/rules/cpp-style.md +++ b/.claude/rules/cpp-style.md @@ -2,6 +2,8 @@ paths: - "dconv/**/*.hpp" - "dconv/**/*.cpp" + - "tools/**/*.hpp" + - "tools/**/*.cpp" --- # C++ style diff --git a/.claude/settings.json b/.claude/settings.json index 70a99a7..a8b4ca4 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -4,42 +4,6 @@ "pr": "" }, "permissions": { - "allow": [ - "Bash(git status:*)", - "Bash(git log:*)", - "Bash(git diff:*)", - "Bash(git show:*)", - "Bash(git branch:*)", - "Bash(git rev-parse:*)", - "Bash(git ls-files:*)", - "Bash(git grep:*)", - "Bash(git blame:*)", - "Bash(cat:*)", - "Bash(head:*)", - "Bash(tail:*)", - "Bash(ls:*)", - "Bash(grep:*)", - "Bash(wc:*)" - ], - "ask": [ - "Bash(git add:*)", - "Bash(git switch:*)", - "Bash(git checkout:*)", - "Bash(git restore:*)", - "Bash(git reset:*)", - "Bash(git stash:*)", - "Bash(git rebase:*)", - "Bash(git cherry-pick:*)", - "Bash(git branch -d:*)", - "Bash(git branch -D:*)", - "Bash(git branch -m:*)", - "Bash(git branch -M:*)", - "Bash(git tag:*)", - "Bash(git worktree:*)", - "Bash(git commit:*)", - "Bash(git push:*)", - "Bash(gh pr create:*)" - ], "deny": [ "Bash(git push --force:*)", "Bash(git push --force-with-lease:*)", diff --git a/AGENTS.md b/AGENTS.md index 6c38da4..34ea5e6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,5 +35,5 @@ Report what ran; never call a partial pass done. A missing tool or wrong version - Brief answers. Change only what was requested; extras as separate proposals. - Plain code first, measured; tricks, fast paths or new helpers only as a measured, proposed option. - Show one diff at a time; edit only after an explicit yes covering that diff ("ok" after an either/or question is not a choice). Reading, building, measuring and scratchpad work need no approval. -- Ask before any state-changing git command and each PR (skill `create-pr`); read-only ones are fine. Never force-push, push to `main` or merge. +- Ask before any state-changing git command (except `<scratchpad>` worktrees for measurements) and each PR (skill `create-pr`); read-only ones are fine. Never force-push, push to `main` or merge. - Never edit `build/` or generated `version.hpp` files. From 410365d584e913b2f0aec8f0b40dbcb3c58cf465 Mon Sep 17 00:00:00 2001 From: mrabine <rabine.mathieu@gmail.com> Date: Wed, 11 Nov 2026 18:52:06 +0100 Subject: [PATCH 5/5] Bump version in every PR and clone benchmarks --- .agents/skills/conformance/SKILL.md | 2 +- .agents/skills/create-pr/SKILL.md | 2 +- .agents/skills/performance/SKILL.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.agents/skills/conformance/SKILL.md b/.agents/skills/conformance/SKILL.md index 00cac18..5a91025 100644 --- a/.agents/skills/conformance/SKILL.md +++ b/.agents/skills/conformance/SKILL.md @@ -7,7 +7,7 @@ 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`. -Ask where `dconv-benchmark` is cloned (`https://github.com/joinframework/dconv-benchmark`); never clone it into the dconv tree. It pins dconv to a release tag, so point both builds at local checkouts, `main` in a worktree: +Clone `https://github.com/joinframework/dconv-benchmark.git` into `<bench>` under `<scratchpad>`, 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 <dconv> fetch origin main && git -C <dconv> worktree add --detach <scratchpad>/base origin/main diff --git a/.agents/skills/create-pr/SKILL.md b/.agents/skills/create-pr/SKILL.md index cb3e46b..cee7594 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` when the library or tools change (not for CI, docs or agent config). Patch for bugfix/hotfix, minor for a feature or an API change, major only on request. Minor or major also sets `PROJECT_NUMBER` = `<major>.<minor>` in `doxyfile`. +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` = `<major>.<minor>` 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 <branch>`. 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 "<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 2322f01..b6876ce 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)?** Ask where `dconv-benchmark` is cloned; never clone it into the dconv tree. +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. ## 2. Performance mode