Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
d3ed77f
docs: Add and polish TYPE_ALGEBRA.md for libfn composition model
Bronek Jul 20, 2026
51fb145
Merge branch 'main' into bronek/draft_TYPE_ALGEBRA
Bronek Jul 23, 2026
26d0a07
docs: Update and polish TYPE_ALGEBRA.md and README.md
Bronek Jul 23, 2026
d84ae8f
docs: Smooth TYPE_ALGEBRA.md style and fold trivial examples
Bronek Jul 23, 2026
00804a4
docs: Unify note style for programmer-facing notes in TYPE_ALGEBRA.md
Bronek Jul 23, 2026
3073750
docs: Fill the monadic operations gap in identity cluster section
Bronek Jul 23, 2026
832826f
docs: Re-organize Section 12 to capture the fail/recover dual symmetry
Bronek Jul 23, 2026
9b0edd8
docs: Update TYPE_ALGEBRA.md introduction to cover disjunction and n-…
Bronek Jul 23, 2026
4ad0ac6
docs: Refine copack multidispatch accuracy and carrier terminology in…
Bronek Jul 23, 2026
fcb7695
docs: Fix constrained template parameters grammar and build examples
Bronek Jul 23, 2026
f419f32
Merge branch 'main' into bronek/draft_TYPE_ALGEBRA
Bronek Jul 23, 2026
9cb0f96
docs: Relax type-ordering in examples and refine pack/copack explanat…
Bronek Jul 24, 2026
092a555
Merge branch 'main' into bronek/draft_TYPE_ALGEBRA
Bronek Jul 24, 2026
f168a2e
docs: Relax type-ordering in examples and refine pack/copack explanat…
Bronek Jul 24, 2026
ea37589
docs: Improve transition flow into the vacuous or_else example
Bronek Jul 24, 2026
6600071
Merge branch 'main' into bronek/draft_TYPE_ALGEBRA
Bronek Jul 24, 2026
b7c187c
docs: Restructure carriers and payloads, add cross-carrier bridging
Bronek Jul 24, 2026
1207707
Minor improvements
Bronek Jul 24, 2026
490d424
Merge branch 'main' into bronek/draft_TYPE_ALGEBRA
Bronek Jul 24, 2026
78fdbc6
Review TYPE_ALGEBRA content for contradictions and repetition
Bronek Jul 24, 2026
9521140
Revise TYPE_ALGEBRA prose and rework the compiled examples
Bronek Jul 25, 2026
9669a72
Unify pre-commit markdown sync scripts and refine documentation
Bronek Jul 25, 2026
3d6dc8a
Generalize markdown example sync into a single mechanism
Bronek Jul 25, 2026
78ecdc1
Remove why stanzas and polish C++ prose in TYPE_ALGEBRA.md
Bronek Jul 25, 2026
1a7c6b0
Clarify operator/fold applicability and symmetric duality in TYPE_ALG…
Bronek Jul 25, 2026
ccde7c4
Deduce the expected operand of the comparison against a value (#382)
Bronek Jul 25, 2026
37f66ec
Reach the uninhabited value side, split the folds, and compare packs …
Bronek Jul 25, 2026
321f8aa
Document conjoin/disjoin changes and other major updates from main
Bronek Jul 25, 2026
01bdb24
Tighten TYPE_ALGEBRA prose and rework the mathematical notes
Bronek Jul 25, 2026
cc38f36
Merge branch 'main' into bronek/draft_TYPE_ALGEBRA
Bronek Jul 25, 2026
94c1981
Align TYPE_ALGEBRA with #385 and continue the editorial pass
Bronek Jul 26, 2026
65a3e50
Cut over-generalizations and decorative emphasis
Bronek Jul 26, 2026
2049238
Name the identity cluster where its members are introduced
Bronek Jul 26, 2026
c5f0d25
Bold terms where they are introduced, and nowhere else
Bronek Jul 26, 2026
75990d7
Fix formulas rendering
Bronek Jul 26, 2026
1334c0e
Revamp CLAUDE.md; move user-relevant guardrails into CONTRIBUTING
Bronek Jul 29, 2026
faaed36
Merge branch 'main' into bronek/draft_TYPE_ALGEBRA
Bronek Jul 29, 2026
428d9b7
Publish the root documents as chapters of the docs site
Bronek Jul 29, 2026
e0bcb53
Merge branch 'main' into bronek/draft_TYPE_ALGEBRA
Bronek Jul 29, 2026
9751d39
Make README the front page of the docs site
Bronek Jul 29, 2026
9f6c312
Give the docs site a section per root document
Bronek Jul 29, 2026
9990f16
Gather the API reference into one section, a page per feature
Bronek Jul 29, 2026
9b0e4d8
Describe the pipeline as it is in the continuous integration section
Bronek Jul 29, 2026
9702ea4
Present each overload set as one listing, with its own documentation
Bronek Jul 30, 2026
6980324
Publish the carrier members the reference never showed
Bronek Jul 30, 2026
4b654dd
Document the carrier specializations and the disjoin fold
Bronek Jul 30, 2026
2818773
Show the vocabulary each carrier makes available
Bronek Jul 30, 2026
2e7c11f
Order each carrier's page the way a reader meets the type
Bronek Jul 30, 2026
be1a3b0
Give the verb objects a description, and render variables as variables
Bronek Jul 30, 2026
fe3ad90
Document and publish the free comparisons and swap
Bronek Jul 30, 2026
40ccce0
Publish operator& and operator| on the fold pages
Bronek Jul 30, 2026
608355e
Point pfn at the standard rather than restating it
Bronek Jul 30, 2026
85862b3
Say where each concept lives, and what a choice is
Bronek Jul 30, 2026
6d3dc9f
Fail the docs build when a description does not reach the site
Bronek Jul 30, 2026
bf85bcf
Name znai in the docs map
Bronek Jul 31, 2026
076c44d
Extend the recency defence to wordiness and delegate its reread
Bronek Jul 31, 2026
d928d54
docs: Stylistically polish and refine TYPE_ALGEBRA.md
Bronek Aug 1, 2026
1a2996d
Close two silent paths in the docs scripts
Bronek Aug 1, 2026
ee0fbd3
docs: Update reference pages and exemptions for operator signature co…
Bronek Aug 1, 2026
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ build/
.scratch*/
dist*/
result*
__pycache__/**
__pycache__/
.cache/
.devcontainer/
.venv/
Expand All @@ -23,3 +23,4 @@ CMakeUserPresets.json
bazel-*
MODULE.bazel.lock
.bazelversion
.memsearch/**
11 changes: 9 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,16 @@ repos:
language: python
files: ^include/.*\.hpp$
pass_filenames: false
# NOTE each document and its example must match a DOCUMENTS entry in scripts/sync_md_examples.py
- id: sync-readme-example
name: sync README example with examples/readme
entry: python scripts/sync_readme_example.py
name: sync README code fences with examples/readme
entry: python scripts/sync_md_examples.py README.md
language: python
files: ^(README\.md|examples/readme/main\.cpp)$
pass_filenames: false
- id: sync-type-algebra-examples
name: sync TYPE_ALGEBRA code fences with examples/type_algebra
entry: python scripts/sync_md_examples.py TYPE_ALGEBRA.md
language: python
files: ^(TYPE_ALGEBRA\.md|examples/type_algebra/main\.cpp)$
pass_filenames: false
60 changes: 37 additions & 23 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,82 @@

Conventions for AI agents in this repo (you are the primary reader — keep this terse).

The project is libfn: a header-only C++20 functional-programming library — `fn` (monadic
composition and types) layered over `pfn` (polyfills of C++23/26 vocabulary types).

## Collaboration

- Pushing back **and** asking questions is welcome — a challenged design beats a silently implemented flawed one.

## CI

- Red CI is top priority — fix before other work; a failed build masks failures behind it. Check CI state when starting new work: via `gh` if available, else ask the user.
- `gh` is **optional** — use it when `GH_TOKEN` is set (repo `libfn/functional`); scope varies per session, so attempt what the user asks and fall back to drafting/asking when a permission blocks. The token is a RAM-only, short-lived PAT injected at launch — never `gh auth login` (persists it to disk).
- Red CI is top priority — fix before other work; a failed build masks failures behind it. At session start (top-level agent, not subagents): check CI state via `gh` if available, else ask the user.
- Before editing `.github/workflows/`, read CONTRIBUTING `## GitHub Actions workflow pitfalls`.
- `gh` works iff `GH_TOKEN` is set; its scope varies per session — on a permission block, fall back to drafting/asking. Never `gh auth login` (would persist the ephemeral token to disk). `gh pr edit` / `gh issue view` SILENTLY no-op under this token — use `gh api` instead.

## Commits
## Commits & GitHub text

- Trailer `Assisted-by: Claude:<exact session model id>` (Linux-kernel convention), e.g. `claude-opus-4-8`. No `Co-Authored-By:`.
- Offer commits; never commit without confirmation. Terse messages: imperative topic, body only if needed.
- Trailer `Assisted-by: Claude:<your live model id>` (Linux-kernel convention), e.g. `claude-opus-4-8`. This replaces the harness's trailer boilerplate entirely — no `Co-Authored-By:`, no `Claude-Session:` URL. Same rule for GitHub issues, PRs and comments: `Assisted-by:` is welcome; no other footers or attribution boilerplate.
- Offer commits; never commit without the user's confirmation — which may be relayed to a commit subagent by the parent that received it. Terse messages: imperative topic; body only when the change needs a *why* (the routing rule in Code names that case).
- If a pre-commit hook rewrites staged files, the commit aborts — re-stage the same files and retry once (CONTRIBUTING `## Pre-commit`).
- A feature or fix commit should include a test for the behaviour it changes; exceptions are allowed. The PR must contain such a test somewhere unless the behaviour is inherently untestable (for example, because of language or compiler limitations); explain the omission.
- Never `git push` or sign commits — the user signs (GPG) and pushes.

## Git state

- Starting work, orient first: `git status -sb` + `git log --oneline -5` — catches silent branch switches; unpushed commits await the user's push. Read-only git is free; `git diff` can be large — use judiciously.
- At session start (top-level agent, not subagents), orient first: `git status -sb` + `git log --oneline -5` — catches silent branch switches; unpushed commits await the user's push. `git diff` can be large — use judiciously.

## Build & verification

- Build/test is CMake + Catch2, one ctest target per test source; toolchain, options and modes: CONTRIBUTING `## Building locally`. Local build trees are gitignored siblings named `.build*` — reuse an existing tree rather than configuring a fresh one per task.
- Watch every gate's output: never send a build or test run to `/dev/null`, and read the failing tail as well as the exit code. Rebuild before rerunning tests — a stale binary passes the tests it was built from.

## Code

- Default to no comment; assume the reader reads the surrounding code. Comment only where the *why* stays non-obvious despite that context (constraint/invariant/workaround/surprise); never restate code; no boilerplate docstrings.
- Routing: *unusual code* → comment; *ordinary code, noteworthy change* → commit body; *both obvious* → neither. "Context" = code the reader sees; why-the-change → commit.
- Don't create `.md`/summary/planning files unless asked.
- Don't create `.md`/summary/planning files unless asked (memory files are exempt — see Memory).
- A new file's copyright year = the year it enters the codebase (the current year; if unsure, infer from the latest commit).
- In `include/` headers, anchor the standard library as `::std::`, never bare `std::` — a user's `fn::std` would otherwise win lookup inside namespace `fn`. Not needed in tests.

## Layering

Four header layers; each may depend only on those below it:
- `include/fn` — may use `fn/detail` and `pfn`
- `include/fn/detail` — may use `pfn`, never `fn`
- `include/pfn` — C++23/26 polyfill; standalone except for the version header
- `include/libfn_version.hpp` — base: the sole root header, no dependencies
- Four header trees under `include/`, strictly layered — `fn` → `fn/detail` → `pfn` → `libfn_version.hpp`; each may depend only on layers after it, never back. Rules and the hoist technique (making an `fn` facility available to `fn/detail`): CONTRIBUTING `## Header layering`.
- Inline-namespace versioning wrap: CONTRIBUTING `## Versioning`; pre-commit enforces.

Every `namespace fn` opening in `include/` carries `inline namespace LIBFN_VERSION`, every `namespace pfn` opening `inline namespace LIBFN_VERSION_BASE` — the mode-less spelling; pfn never joins the `_cxx26` ABI twin (pre-commit enforced). A header that opens either includes `<libfn_version.hpp>` itself.
## C++ standard versions

To give an `fn/detail` file something that lives in `fn`, hoist it: the implementation moves into `fn/detail/X.hpp` as `fn::detail::_name` (no doxygen — detail headers aren't user-facing); `fn/X.hpp` stays a thin public wrapper re-exporting it as `fn::name` (pattern: `fn/functional.hpp`).
- C++20 is the baseline: `include/` relies on C++20 only in every default-mode build, and `pfn` never relies on post-C++20 features in any mode. Spell C++23-isms as C++20: `static operator()` → `const` member; a `static constexpr` local in a constexpr function → non-static; `std::unreachable` → `pfn::unreachable`; `0uz` → `std::size_t{0}`.
- `LIBFN_CXX26` (strict opt-in): C++26 reliance — `std::type_order` behind its capability gate; details in CONTRIBUTING `### Standard-mode feature reliance`.

## C++20
## Client code

C++20 is the sole export surfacefn + pfn build and pass tests as C++20 on all supported compilers, incl. MSVC; CI validates C++23 via the test-only `VALIDATE_CXX23` lanes. Keep `include/` C++20 — spell C++23-isms as C++20: `static operator()` → `const` member; a `static constexpr` local in a constexpr function → non-static; `std::unreachable` → `pfn::unreachable`; `0uz` → `std::size_t{0}`.
- Code written against the libraryexamples, documentation snippets, reproducers, anything a user of libfn might imitate: follow CONTRIBUTING `## Client code`.

## Tests

- Before writing or reviewing tests, read and follow `CONTRIBUTING.md` from `## Unit tests` to the next top-level heading; it is the source of truth for test structure, assertions and compile-time probes.

## Delegation

- Delegate mechanical, well-specified, verifiable work — builds, test sweeps, commit mechanics, compile probes — to subagents by default; their noise stays out of the working context. Run independent delegations concurrently.
- Keep exploratory reading, design and diagnosis inline: a subagent returns its result, not the understanding built producing it, and for this work the understanding is the point.
- Local agent definitions may exist under `.claude/agents/` (not committed); prefer them when present. Agents register at session start — a new or edited definition is invisible until the session restarts.
- Verify a finding empirically before filing it anywhere; for a compile-time claim that means a probe compiled on both gcc and clang.

## Tooling

- Prefer `clangd-lsp@claude-plugins-official` over grep/whole-file reads for C++ symbol navigation (go-to-def, find-refs) and post-edit diagnostics — targeted lookups should cut context, not add it. Needs a populated `compile_commands.json`; if unavailable/empty, ask the user to populate it and offer help.
- When the `clangd-lsp@claude-plugins-official` plugin is available, prefer it over grep/whole-file reads for C++ symbol navigation (go-to-def, find-refs) and post-edit diagnostics — targeted lookups should cut context, not add it. Needs a populated `compile_commands.json`; if unavailable/empty, ask the user to populate it and offer help.
- clangd reflects one local toolchain, not the CI matrix — a clean clangd buffer is NOT portability clearance; full `-Werror` builds + CI stay the authority.

## Memory

- Keep memory current as facts change.
- Create memory files without asking, but announce each one and its purpose.
- On wrap-up or a "memory pass" request: review memory — update/remove obsolete, flag new.
- On wrap-up or a "memory pass" request: curate memory — update or remove obsolete entries, capture new durable facts; announce each new memory file and its purpose. A "consolidation pass" request does the same against recent session journals.

## Docs

- Map: README.md = user-facing overview (purpose, usage, project shape, support surface; no agent directives, no internal mechanics; CI surfaced as evidence only, never mechanics); CONTRIBUTING.md = contributor facts (coding + tests standards, build environment, workflows, all CI details, mechanics of every aspect; no agent directives, no library usage); CHANGELOG.md = design history (dated entries, newest first); docs/ = API reference (Doxygen → Pages; also usage); CLAUDE.md = agent practice + the critical selection of standards (coding, tests, documentation).
- Living documents (README, CONTRIBUTING, docs/) are timeless present tense — no "now", "no longer", "previously"; when reading or updating them, remove recency bias. A change that obsoletes documented design gets a dated CHANGELOG.md entry in the same change, saying what it obsoleted and why.
- Recency bias defence (all documentation except CHANGELOG.md, and code comments): you over-weight whatever you just worked on, so text written right after a change reads as a diff against your context, not a document for a reader who arrives fresh. The banned transition words are only the shallow symptom; test deeper: (1) day-one test — would this sentence exist had the feature or fix always been here? if not, cut it; (2) effort test — is the detail sized by reader need, or by how hard the work was? cut whatever answers questions no reader asked; (3) placement test — is it where a newcomer would look, or where your recent work pulls it? Defence: after editing, reread the whole file top-to-bottom as a first-time reader and re-judge the new text's length and position against the whole document — never review only your diff. CHANGELOG.md and commit messages are exempt: both are read as an increment on top of previous state, so change-perspective is their correct form.
- Map: README.md = user-facing overview (purpose, usage, project shape, support surface; no agent directives, no internal mechanics; CI surfaced as evidence only, never mechanics); CONTRIBUTING.md = contributor facts (coding + tests standards, build environment, workflows, all CI details, mechanics of every aspect; no agent directives, no library usage); TYPE_ALGEBRA.md = the design document — the library's type algebra worked from first principles; CHANGELOG.md = design history (dated entries, newest first); docs/ = API reference (Doxygen + znai → Pages; also usage); CLAUDE.md = agent practice + guardrails pointing into the above.
- Fenced C++ examples in README.md and TYPE_ALGEBRA.md are generated from sources in `examples/` by `scripts/sync_md_examples.py` (pre-commit keeps them in sync) — edit the example source, never the fence; prose edits stay outside fences.
- Living documents (README, CONTRIBUTING, docs/) are timeless present tense — no "now", "no longer", "previously". A change that obsoletes documented design gets a dated CHANGELOG.md entry in the same change, saying what it obsoleted and why.
- Recency-bias and wordiness defence (all human-readable text — docs, code comments, and the like; CHANGELOG.md and commit messages are exempt — change-perspective is their correct form): before keeping new text, test — (1) day-one: would this sentence exist had the feature or fix always been here? (2) effort: is detail sized by reader need, or by how hard the work was? — a hard-won bugfix earns no extra words; its history lives in `git log`/`git blame`/CHANGELOG.md; (3) placement: is it where a newcomer looks, or where your recent work pulls it? (4) economy: could fewer words say it as well? After editing, delegate a whole-file top-to-bottom reread to a subagent briefed as a first-time reader, blind to what changed — never review only your diff. Triage its findings: fix what your edit touches, surface the rest rather than rewriting unasked.
- On memory or practice changes, check the root `.md` files for drift from reality and **offer** fixes (CLAUDE.md = practice, README/CONTRIBUTING = facts).
25 changes: 24 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,29 @@ Match the assertion to the kind of fact being tested.

Choose fixtures with care when probing exception specifications. `helper_t` has a separate constructor for each value category, and its non-const-lvalue copy constructor is always `noexcept`. A `helper_t` configured to throw is therefore not throwing for every value category, and a specification that remains `noexcept` for that constructor may be correct. Use a plain local type when the test needs every relevant construction to be potentially throwing.

## Client code

Code written against the library — examples, documentation snippets, reproducers, anything a user of libfn might imitate:

* Let the library derive graded error types. Never spell a multi-alternative `copack<...>` by hand — the canonical alternative order is internal and platform-dependent (MSVC orders class types after fundamentals); use `copack_for<...>`, and pin a deduced type with `static_assert` where the type is the point.
* Give behaviour a constant-evaluation twin: a `static_assert` replaying the same operations. UB is ill-formed in a constant expression, so the compiler diagnoses what a runtime run may silently get wrong — users rely on the library in constexpr for exactly this.
* Prefer monadic composition to unchecked access. `value()` is the library's only throw; reaching for it where `and_then`/`transform` would carry the value teaches the wrong idiom.
* Don't trust a bare `requires`-probe of a libfn call: several refusals (`apply` on mismatched branches, `and_then` on grade mismatches) are `static_assert`s inside the callee, so the probe answers true and instantiation hard-errors. Compile the call to know; negative viability probes must be dependent (see `### Compile-time probes` above).
* Outside `LIBFN_CXX26`, the internal type ordering is not expected to work with unnamed types or types without linkage — no lambdas or local types as `copack` alternatives in portable code.
* One libfn version per binary: header-only makes mixing versions an ODR violation, including two `z` releases of the same `y` line; default and `LIBFN_CXX26` builds never link as one, by design — see README `## Versioning and ABI`.
* A standalone reproducer (compiler or library bug) proves nothing until it is UB-free: gate it on UBSan and ASan with empty stderr as the criterion, not the exit code.

## Header layering

Four header trees under `include/`, each depending only on those below it:

* `fn` — may use `fn/detail` and `pfn`
* `fn/detail` — may use `pfn`, never `fn`
* `pfn` — the C++23/26 polyfill; standalone except for the version header
* `libfn_version.hpp` — the sole root header, no dependencies

To make an `fn`-level facility available to `fn/detail`, hoist it: the implementation moves into `fn/detail/X.hpp` as `fn::detail::_name` (detail headers are not user-facing and carry no Doxygen); `fn/X.hpp` remains a thin public wrapper re-exporting it as `fn::name` (pattern: `fn/functional.hpp`).

## Versioning

`VERSION` (in the repository root) is the single source of truth for the project version. A pre-commit hook (`scripts/sync_versions.py`) mirrors it into `ports/libfn/vcpkg.json` (`version-semver`), `MODULE.bazel`, and `include/libfn_version.hpp` — the header defining the `LIBFN_VERSION` macro that names the ABI-versioning inline namespace wrapping `fn`, and its mode-less sibling `LIBFN_VERSION_BASE` wrapping `pfn` (the layer rule below). Do **not** hand-edit those version literals — edit `VERSION` and let the hook sync them.
Expand All @@ -75,7 +98,7 @@ This repository uses [pre-commit](https://pre-commit.com/) to enforce formatting
python3 -m venv .venv
source .venv/bin/activate
pip install -r ci/pre-commit/requirements.txt
# Now install the pre-commit hooks locally
# Install the pre-commit hooks locally
pre-commit install
```

Expand Down
Loading
Loading