Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .agents/skills/add-gtest/SKILL.md
Original file line number Diff line number Diff line change
@@ -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/<header>_test.cpp` (or `<class>_test.cpp` when a header holds several classes) with one `TEST (<suite>, <function>)` per function (suite = namespace, header or class; constructors in `create`, assignments in `assign`), each documented with a three-line `/**` / ` * @brief <function> 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(<name>.gtest <name>_test.cpp)
target_link_libraries(<name>.gtest ${PROJECT_NAME} GTest::gtest_main pthread)
gtest_discover_tests(<name>.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.
28 changes: 28 additions & 0 deletions .agents/skills/compile/SKILL.md
Original file line number Diff line number Diff line change
@@ -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 <preset>
cmake --build --preset <preset> [--target <name>.gtest]
```

Presets: `gcc-` / `clang-` + `debug` (coverage), `release`, `asan` (ASan + UBSan); trees in `build/<compiler>/<kind>`. 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.
32 changes: 32 additions & 0 deletions .agents/skills/conformance/SKILL.md
Original file line number Diff line number Diff line change
@@ -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`.

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
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
<build>/benchmarks/conformance/conformance --output <scratchpad>/conformance-<main|branch>.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.
32 changes: 32 additions & 0 deletions .agents/skills/coverage/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
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 <scratchpad>/$C-after.json --print-summary build/$C/debug
```

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, 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 <scratchpad>/coverage.sh
```

Same for the baseline with `-w <scratchpad>/base`. If neither works, report coverage as **not verified**, never "not degraded".
22 changes: 22 additions & 0 deletions .agents/skills/create-pr/SKILL.md
Original file line number Diff line number Diff line change
@@ -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 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.
- `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` = `<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`.
8. **Report** branch, commit, presets run and the link; do not claim CI is green.
30 changes: 30 additions & 0 deletions .agents/skills/gen-tables/SKILL.md
Original file line number Diff line number Diff line change
@@ -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 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 results.
34 changes: 34 additions & 0 deletions .agents/skills/performance/SKILL.md
Original file line number Diff line number Diff line change
@@ -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)?** Clone `dconv-benchmark` there as in skill `conformance`, never 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.
22 changes: 22 additions & 0 deletions .agents/skills/release/SKILL.md
Original file line number Diff line number Diff line change
@@ -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).
21 changes: 21 additions & 0 deletions .agents/skills/test/SKILL.md
Original file line number Diff line number Diff line change
@@ -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.
28 changes: 28 additions & 0 deletions .claude/agents/explorer.md
Original file line number Diff line number Diff line change
@@ -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.
27 changes: 27 additions & 0 deletions .claude/agents/reviewer.md
Original file line number Diff line number Diff line change
@@ -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.
17 changes: 17 additions & 0 deletions .claude/hooks/format.sh
Original file line number Diff line number Diff line change
@@ -0,0 +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
exit 0
Loading
Loading