Files
libakstdlib/AGENTS.md
Tachikoma 58f426abce
All checks were successful
libakstdlib CI Build / coverage (push) Successful in 2m47s
libakstdlib CI Build / sanitizers (push) Successful in 2m57s
libakstdlib CI Build / cmake_build (push) Successful in 3m15s
libakstdlib CI Build / mutation_test (push) Successful in 12m35s
Document libc wrapper contract
Co-authored-by: Andrew Kesterson <andrew@aklabs.net>
2026-08-03 06:38:21 -04:00

7.9 KiB

Repository Guidelines

Project Structure & Module Organization

libakstdlib is a C shared library that wraps libc calls and data structures in libakerror error contexts.

Public API declarations live in include/akstdlib.h, which is also where the Doxygen documentation lives -- the header is the contract, the sources carry the reasoning. The implementation is split by domain:

File Covers
src/stdlib.c memory, formatted output, string-to-number, realpath, djb2, and the list/tree traversal entry points
src/string.c the string.h surface
src/stream.c stdio.h beyond open/read/write/close
src/collections.c list and tree operations, hash map, string buffer, FNV-1a
src/aksl_internal.h shared internals; not installed, not public

Tests are one-file CTest executables under tests/test_<name>.c, with shared helpers in tests/aksl_capture.h. tests/negative/ holds sources that must fail to compile, and tests/consumer/ is a standalone project built against the installed package rather than the build tree -- see the testing section below. CMake package templates are in cmake/ and akstdlib.pc.in. The vendored dependency is deps/libakerror; update it as a submodule rather than editing generated files under build/, build-asan/ or build-coverage/.

Build, Test, and Development Commands

Initialize dependencies before a fresh build:

git submodule update --init --recursive
cmake -S . -B build
cmake --build build

Run the normal suite with ctest --test-dir build --output-on-failure. Use the instrumented build for memory and undefined-behavior checks:

cmake -S . -B build-asan -DAKSL_SANITIZE=ON
cmake --build build-asan
ctest --test-dir build-asan --output-on-failure

For coverage, configure a third tree; the report is part of that suite (the coverage_reset / coverage_report CTest entries) and also lands in build-coverage/coverage-summary.txt:

cmake -S . -B build-coverage -DAKSL_COVERAGE=ON
cmake --build build-coverage --target coverage

Run cmake --build build --target mutation only when you need the slower mutation harness, and cmake --build build --target docs to regenerate the API documentation -- that one fails if any public function, parameter or return value is undocumented, so it is a check as well as a generator.

.githooks/pre-push runs the default build, the sanitizer build and the documentation check before a push; enable it with git config core.hooksPath .githooks. rebuild.sh installs to /home/andrew/local and removes the local build directory, so treat it as a local convenience script.

Coding Style & Naming Conventions

Use C with 4-space indentation; existing files sometimes use tabs for continued statements, so match the surrounding block. Public symbols use the aksl_ prefix, structs use aksl_<Name>, and tests use test_<feature>.c plus static test_<case> functions. Preserve the akerr_ErrorContext AKERR_NOIGNORE * return convention and the PREPARE_ERROR / FAIL_* / SUCCEED_RETURN pattern.

Four conventions hold across the whole library, and a new wrapper that breaks one of them is wrong even if it compiles and passes:

  • Preserve the libc contract unless there is a compelling, documented reason not to. libc behaviour is the standard to meet. This library changes only the error transport (to akerror) and, where libc returns a value, the result shape (through a caller-provided destination pointer).
  • A NULL out-param is a caller error, not "don't care".
  • Finding nothing is success -- searching functions write NULL or zero and return NULL.
  • Truncation is a failure. Take the destination's size, raise AKERR_OUTOFBOUNDS, and write nothing rather than a prefix.
  • Clear errno before the wrapped call and read it back through AKSL_ERRNO_OR, so no error can carry status 0 -- which every downstream DETECT reads as success.

Every new public function needs a Doxygen block on its declaration with @brief, a @param per parameter, a @throws per status it can raise, and @return. The docs target fails otherwise.

The build is -Wall -Wextra and CI adds -Werror. -Wpedantic is deliberately off: libakerror's FAIL_* macros trip "ISO C99 requires at least one argument for the ..." on their own expansion, not on anything at the call site.

Do not wrap a libc function that cannot fail and provides no failure or operation-status result. There is no akerror context to carry. A value such as umask()'s previous mask is not an operation-status result, so umask() is not a wrapper candidate.

Testing Guidelines

Add a new test by creating tests/test_mything.c and adding mything to the right list in CMakeLists.txt. AKSL_TESTS must exit zero. AKSL_WILL_FAIL_TESTS are deliberate abort/contract tests. AKSL_KNOWN_FAILING_TESTS assert defects that have an open issue; when one starts unexpectedly passing, move it into AKSL_TESTS with the fix and close the issue. Both of the latter are currently empty -- all six confirmed defects are fixed -- but the mechanism stays for the next one.

tests/negative/ holds sources that must fail to compile. Each is an EXCLUDE_FROM_ALL target built with -Werror and registered as a WILL_FAIL CTest entry, so the test passes only when the compile fails. They cover the two guarantees the compiler enforces and nothing else does: AKERR_NOIGNORE and the format attributes. Drop either in a refactor and every ordinary test still passes.

tests/consumer/ is configured standalone against CMAKE_PREFIX_PATH, not as part of this build, because being part of this build is what would let it pass without testing anything. It covers the paths only an install has: the version file, find_dependency(akerror) resolving, and the exported akstdlib::akstdlib target. CI runs it after cmake --install.

Coverage is 99.5% of lines and 100% of functions across all four sources; CI gates at 90 (line) / 40 (branch), so new code needs tests in the same commit. Run cmake --build build-coverage --target coverage and check the uncovered-line listing before proposing a change. Tests for behaviour an open issue records as defective belong in AKSL_KNOWN_FAILING_TESTS asserting the correct contract — do not pin current-but-wrong behaviour in AKSL_TESTS, since that turns the eventual fix into a test failure.

Commit & Pull Request Guidelines

Recent commits use short imperative summaries, for example Add memory wrapper tests and Make error-status assertions authoritative. Keep commits focused and include tests with behavior changes. Pull requests should describe the changed API or behavior, list the CTest/sanitizer/mutation commands run, and link the issue it closes.

Agent-Specific Instructions

Outstanding work goes in the issue tracker, not in a file. Open an issue at https://source.starfort.tech/andrew/libakstdlib/issuestea issues create --repo andrew/libakstdlib — naming the file and line, the functional consequence, and what closing it would touch. Label it by kind and blast radius and leave status::grooming on it until its scope and approach are settled. Do not add outstanding items to TODO.md: that file is the record of where the library stands, what is deliberately not wrapped, and which uncovered lines are uncoverable rather than untested. A description of work still to do goes stale the moment somebody does it, which is why the two are separated.

A defect in a dependency is filed against that dependency. libakerror has a tracker on the same forge, and three defects that cost this library a workaround each sat in this repository's own notes for months without anybody upstream being able to see them. Comment the workaround at its site with the words "filed upstream" and delete it when the fix lands.

Do not modify generated build trees, profiling artifacts, or untracked scratch files unless explicitly asked. Prefer small, test-backed changes and update README.md when changing documented workflows.