Skip to content

Clean up inconsistencies in public API and documentation#88

Draft
DRMacIver wants to merge 11 commits into
mainfrom
claude/api-cleanup
Draft

Clean up inconsistencies in public API and documentation#88
DRMacIver wants to merge 11 commits into
mainfrom
claude/api-cleanup

Conversation

@DRMacIver

Copy link
Copy Markdown
Member
Implementation details (AI-generated)

Fixes from a documentation-vs-behavior audit of the public API:

  • New hegel/datetime.h with typed hegel::Date/Time/DateTime; the temporal generators return these instead of strings (zerover, breaking change). C++17-clean, with six-digit fractional-second formatting matching hegel-rust.
  • filter() docs describe the real semantics: three attempts per draw, then the test case is rejected (not "retries until satisfied").
  • Registering two HEGEL_REPRODUCE_FAILURE handlers for the same test now throws instead of silently ignoring the second; first-wins replay behavior is kept and documented.
  • run_all_tests no longer aborts on a non-std::exception throw — it is caught and counted as a failure like any other.
  • randoms() docs rewritten to describe what it actually is (engine-backed PRNG draws, replayable), with a prominent lifetime warning; the previous description of a Hypothesis-style wire protocol was fiction.
  • override() on derived generators genuinely skips the overridden fields' default draws (verified by draw-trace counting) instead of drawing and discarding.

Verification: 322/322 ctest, the C++17 consumer compile check, and format check pass. clang-tidy and doxygen jobs were not runnable in the audit environment — CI should confirm those two.

🤖 Generated with Claude Code

DRMacIver and others added 11 commits July 10, 2026 09:52
The engine produces structured date/time values, but the wrappers
serialized them to ISO 8601 strings and discarded the structure — and
the string shape was bimodal (fractional seconds appeared only when the
microsecond was nonzero, copying Python's isoformat() wart).

Introduce public hegel::Date, hegel::Time, and hegel::DateTime
aggregates (C++17-compatible) with chronological comparison operators,
to_string(), and operator<<, and change the three generators to return
them. to_string() always prints the six-digit fractional-seconds field,
so every value now has exactly one canonical serialization.

Breaking change: dates()/times()/datetimes() previously returned
Generator<std::string>; use to_string() or .map(...) to recover the
string form.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The previous doc claimed Hegel "doesn't let you" filter — describing
the exact method it was documenting — and repeated a garbled duplicate
of the same paragraph. Replace it with an accurate description: up to
three attempts per draw with rejected attempts discarded, then the
whole test case is rejected via assume(false) (subject to the
FilterTooMuch health check), plus the standard advice to prefer
construction over filtration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
register_blob stored only the first blob of the vector it was handed
and silently no-op'd when a second HEGEL_REPRODUCE_FAILURE was
registered for the same test, so extra blobs and later registrations
vanished without a trace.

Matching the family convention (hegel-rust documents stacked
reproduce_failure attributes as first-wins bookkeeping), the vector is
kept and only the first blob is replayed — but now:

- the registry retains every blob passed to HEGEL_REPRODUCE_FAILURE,
  so the bookkeeping blobs are not lost;
- a duplicate same-test registration throws std::logic_error with a
  message naming the test (terminating loudly during static
  initialization) instead of being silently dropped;
- the hegel::test() and HEGEL_REPRODUCE_FAILURE docs state the
  first-wins rule unambiguously.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ll_tests

Since 0.5.0 a single failing example rethrows the test body's ORIGINAL
exception, but the @throws doc still claimed std::runtime_error was
thrown whenever a test case fails. Document the actual contract: the
body's own exception is rethrown as-is, and std::runtime_error is
reserved for run errors, non-reproducing blobs, and the
multiple-failures report.

run_all_tests() caught only const std::exception&, so a registered test
whose body threw anything else (an int, a string literal, a custom
type) escaped the sweep and terminated the process — violating its
documented "returns 1 if any test failed". Catch everything, count it
as a failure, and report the demangled exception type to stderr.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The docs described Hypothesis and the pre-0.4.0 subprocess protocol
("routing every draw through Hypothesis"), simultaneously advertised
use with any <random> distribution and warned that doing so can hang,
and documented a parameter named tc as @param data. They also never
mentioned that a default-mode HegelRandom holds a pointer into the
TestCase and must not outlive the test-case callback.

Rewrite them from scratch: what the default (test-case-backed,
shrinkable) mode does, what true-random mode does, one coherent
recommendation (use true-random mode with <random> distributions and
whenever the value distribution matters; default mode for code that
consumes the raw bits), the corrected @param name, and a prominent
lifetime warning.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The doc said "(used by TUI)", but no TUI exists in this project.
Document the real behavior: Quiet suppresses notes, drawn-value
replay output, failure headers, and reproduction blobs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…fields

override() claimed to "replace" a field's generator but actually drew
the full default struct first and assigned the override's value over
it. The dead default draw still consumed entropy, cost, and
health-check pressure, and left a junk draw in the choice sequence
that hampered shrinking.

Rebuild the struct draw field-by-field instead: each field consults
the accumulated overrides (matched by member address) and draws only
from its override generator when one exists; the default generator for
that field is never invoked. Chained override() calls now extend the
override set rather than stacking a second full draw, and the most
recent override of a field wins — the observable values are unchanged
from before (later assignment already won), but only the winning
generator draws.

Regression test counts engine draws through the Verbose draw trace:
a Person with `name` overridden by a draw-free just() must log exactly
one "Generated:" line (the remaining int field), where the old
behavior logged two.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The README showed `Generated: [0,0]`, but the library never renders a
composite value: the draw trace prints one `Generated:` line per
primitive draw. It also showed a `Hegel test failed:` prefix that no
longer exists (the test's own exception is rethrown since 0.5.0) and a
platform-specific libc++abi terminate line.

Replace the block with the output of actually running the example
(Linux/libstdc++), and explain what the per-primitive lines mean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follows the repo's changelog convention (RELEASE-sample.md): the
release automation folds this file into CHANGELOG.md with the next
version number, so user-visible changes are documented there in the
existing format.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Formatting-only pass over the files touched by the preceding commits,
via `just format`, so `just check-format` passes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
generators.cpp uses hegel::Date/Time/DateTime but picked hegel/datetime.h
up only transitively, which misc-include-cleaner rejects; include it
directly. datetime.h's comparison operators, stream inserters, and
to_string() methods were missing the doc comments that the Doxyfile's
WARN_AS_ERROR + WARN_NO_PARAMDOC settings require for every public
symbol, parameter, and return value.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@DRMacIver DRMacIver force-pushed the claude/api-cleanup branch from b68b116 to 18a13de Compare July 10, 2026 10:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant