The agent instructions now say where new work goes -- an issue on the forge, labelled by kind and blast radius and carrying status::grooming until its scope is settled -- and that TODO.md is the record of where the library stands, what is deliberately not wrapped, and which uncovered lines are uncoverable rather than untested. Also: a defect in a dependency is filed against that dependency. Three defects in libakerror that cost this library a workaround each sat in this repository's own notes without anybody upstream being able to see them, which is how a workaround gets written once per consumer. The KNOWN_FAILING_TESTS and pull-request rules now name the issue rather than a TODO entry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
7.4 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:
- 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
errnobefore the wrapped call and read it back throughAKSL_ERRNO_OR, so no error can carry status 0 -- which every downstreamDETECTreads 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.
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/issues — tea 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.