Skip to content
Merged

v2 #1

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
6 changes: 4 additions & 2 deletions .agents/skills/coder/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ Read `AGENTS.md` first. This skill adds implementation guidance only.
1. Inspect the touched code, build shape, and tests.
2. Keep interfaces simple and behavior-oriented.
3. Implement the narrowest change that solves the problem.
4. Add or update tests close to the changed behavior.
5. Run relevant build/test/analyzer targets.
4. Add or update tests close to the changed behavior using `frame_test.h`
macros (`FRAME_TEST`, `FRAME_EXPECT_EQ`, `FRAME_EXPECT_TRUE`, etc.).
5. Run `format` before committing, then run relevant build/test/analyzer
targets.

## C++ Rules

Expand Down
3 changes: 3 additions & 0 deletions .agents/skills/project-config-and-tests/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ small deterministic test coverage.
- keep config parsing non-fatal where that preserves recovery/help paths
- keep defaults, example config, and docs aligned
- prefer deterministic tests around parsing, normalization, and helper seams
using `frame_test.h` macros
- keep WHAT/HOW/WHY commentary current in repo-owned tests
- add benchmarks for performance-sensitive helpers using `frame_bench.h`
- use the `coverage` preset and target to verify test coverage for new code
4 changes: 3 additions & 1 deletion .agents/skills/project-core-dev/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ repo-owned C++ code.

## Validation

- build out of tree
- prefer `cmake --preset dev` for development builds
- run `ctest --output-on-failure` for covered changes
- run `cmake --build "$BUILD_DIR" --target format-check` before committing
- run `frame_cli --help` as a lightweight smoke test
- add docs/analyzer/Valgrind validation when the change surface justifies it
- run `cppcheck` target when available for additional static analysis
24 changes: 24 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
BasedOnStyle: LLVM
IndentWidth: 4
ColumnLimit: 100
BreakBeforeBraces: Custom
BraceWrapping:
AfterFunction: true
AfterNamespace: false
AfterStruct: true
AfterClass: true
AfterEnum: true
BeforeElse: true
BeforeCatch: true
SplitEmptyFunction: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AllowShortLambdasOnASingleLine: Inline
AlwaysBreakTemplateDeclarations: Yes
PointerAlignment: Right
ReferenceAlignment: Pointer
SpaceAfterCStyleCast: false
SpaceBeforeParens: ControlStatements
IncludeBlocks: Preserve
SortIncludes: CaseSensitive
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
clang-format \
cmake \
doxygen \
g++ \
Expand All @@ -41,6 +42,9 @@ jobs:
- name: Configure
run: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD=23

- name: Check formatting
run: cmake --build build --target format-check

- name: Build
run: cmake --build build

Expand Down
76 changes: 70 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ Baseline shape:
- Out-of-tree builds are the default workflow
- Repo-owned code should stay portable, local-first, and easy to validate
- One small library target plus one CLI target form the default example shape
- Deterministic `CTest`, `clang-tidy`, Doxygen, release-hygiene, and Valgrind
lanes are part of the maintained contract
- Deterministic `CTest`, `clang-tidy`, `clang-format`, `cppcheck`, Doxygen,
release-hygiene, Valgrind, and coverage lanes are part of the maintained
contract
- Qt/Clazy provide the example UI stack, not the baseline assumption
- Public docs are generated from repo-owned headers and `docs/mainpage.md`
- Feature plans live under `upcoming_features/` as tracked Markdown files only
(see `upcoming_features/TEMPLATE.md` for the expected format)
- CMake presets provide named configurations for common workflows

Repository principles:

Expand All @@ -31,20 +34,33 @@ Repository principles:
## Key Paths

- `src/`: repo-owned library and CLI code
- `tests/`: deterministic example tests and test registration
- `tests/`: deterministic example tests, test registration, and `frame_test.h`
micro-framework
- `docs/`: Doxygen config and API-focused main page
- `scripts/`: hygiene, release, and diagnostics helpers
- `contrib/`: optional service/desktop integration examples
- `cmake/`: reusable analyzer helper scripts
- `.agents/skills/`: project-local agent overlays and merged skills
- `.github/workflows/`: generic CI and release workflow templates
- `benchmarks/`: optional chrono-based micro-benchmarks and `frame_bench.h`
harness
- `upcoming_features/`: forward-looking implementation plans

## Build And Validation

Read `README.md` first before changing build, setup, or release behavior.

Use an out-of-tree build:
Prefer CMake presets for common workflows:

```bash
cmake --preset dev
cmake --build build/dev -j"$(nproc)"
```

Available presets: `dev` (debug + sanitizers), `release` (optimized + LTO),
`ci` (matches GitHub Actions), `coverage` (gcov instrumentation).

Manual out-of-tree build (when presets are unavailable):

```bash
BUILD_DIR="$(mktemp -d /tmp/cpp-frame-build-XXXXXX)"
Expand All @@ -54,7 +70,7 @@ cmake --build "$BUILD_DIR" -j"$(nproc)"

If `ninja-build` is unavailable, omit `-G Ninja`.

Optional sanitizer lane:
Optional sanitizer lane (included in the `dev` preset):

```bash
cmake -S . -B "$BUILD_DIR" -G Ninja \
Expand All @@ -74,7 +90,9 @@ Use the smallest validation set that proves the change, then extend as needed:
- `cmake --build "$BUILD_DIR" --target clang-tidy`
- `cmake --build "$BUILD_DIR" --target clazy` when the project uses the
example Qt-based UI stack and the tool is available
- `cmake --build "$BUILD_DIR" --target lint`
- `cmake --build "$BUILD_DIR" --target format-check`
- `cmake --build "$BUILD_DIR" --target lint` (includes clang-tidy and cppcheck
when available)
- `cmake --build "$BUILD_DIR" --target docs`
- `bash scripts/run-valgrind.sh "$BUILD_DIR"`
- `bash scripts/check-release-hygiene.sh`
Expand All @@ -86,9 +104,55 @@ INSTALL_DIR="$(mktemp -d /tmp/cpp-frame-install-XXXXXX)"
cmake --install "$BUILD_DIR" --prefix "$INSTALL_DIR"
```

## Formatting

Run `cmake --build "$BUILD_DIR" --target format` before committing to keep
source files consistently formatted. CI enforces `format-check` and will reject
unformatted code. The `.clang-format` config at the repo root defines the
canonical style.

## Coverage

Build with the `coverage` preset and run the `coverage` target:

```bash
cmake --preset coverage
cmake --build build/coverage
cmake --build build/coverage --target coverage
```

This runs tests and collects coverage via `gcovr`. HTML output lands in
`build/coverage/coverage/`. New code should maintain or improve line coverage.

## Benchmarks

Build with `-DFRAME_ENABLE_BENCHMARKS=ON` and run manually:

```bash
cmake --build "$BUILD_DIR" --target example_bench
"$BUILD_DIR/benchmarks/example_bench"
```

Add benchmarks for hot paths, algorithms, and performance-sensitive code.
Use `FRAME_BENCHMARK(name, iterations)` from `benchmarks/frame_bench.h`.

## Project Setup

When adapting this frame for a new project, use the init script:

```bash
./scripts/init-project.sh --name "Your Project Name" # dry-run
./scripts/init-project.sh --name "Your Project Name" --apply
```

This renames all placeholder targets, namespaces, prefixes, and filenames.

## Testing Rules

- Keep repo-owned tests deterministic and headless under `CTest`
- Use the `frame_test.h` micro-framework: `FRAME_TEST(name)` for registration,
`FRAME_EXPECT_EQ`, `FRAME_EXPECT_TRUE`, `FRAME_EXPECT_FALSE`,
`FRAME_EXPECT_THROWS` for assertions, `FRAME_RUN_TESTS()` in main
- Keep `WHAT/HOW/WHY` commentary near the start of real test bodies; the repo
scripts enforce that contract
- Prefer pure helper seams and injected fakes over environment-heavy tests
Expand Down
88 changes: 83 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ set(CMAKE_CXX_EXTENSIONS OFF)

option(FRAME_ENABLE_ASAN "Enable AddressSanitizer for repo-owned code" OFF)
option(FRAME_ENABLE_UBSAN "Enable UndefinedBehaviorSanitizer for repo-owned code" OFF)
option(FRAME_ENABLE_COVERAGE "Enable gcov/llvm-cov coverage instrumentation" OFF)
option(FRAME_ENABLE_BENCHMARKS "Build the benchmarks directory" OFF)
option(FRAME_ENABLE_QT "Enable the example Qt-based UI integration path" OFF)
option(FRAME_ENABLE_CLAZY "Enable the clazy analyzer lane for the Qt-based UI path" OFF)

Expand Down Expand Up @@ -41,7 +43,7 @@ set_target_properties(frame_cli PROPERTIES
OUTPUT_NAME frame_cli
)

function(frame_enable_sanitizers target_name)
function(frame_enable_sanitizers target_name visibility)
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
message(WARNING "Sanitizers were requested, but ${CMAKE_CXX_COMPILER_ID} is not configured for repo-owned sanitizer flags")
return()
Expand All @@ -61,13 +63,28 @@ function(frame_enable_sanitizers target_name)
endif()

if(sanitizer_compile_flags)
target_compile_options(${target_name} PRIVATE ${sanitizer_compile_flags})
target_link_options(${target_name} PRIVATE ${sanitizer_link_flags})
target_compile_options(${target_name} ${visibility} ${sanitizer_compile_flags})
target_link_options(${target_name} ${visibility} ${sanitizer_link_flags})
endif()
endfunction()

frame_enable_sanitizers(project_core)
frame_enable_sanitizers(frame_cli)
frame_enable_sanitizers(project_core PUBLIC)
frame_enable_sanitizers(frame_cli PRIVATE)

function(frame_enable_coverage target_name visibility)
if(NOT FRAME_ENABLE_COVERAGE)
return()
endif()
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
message(WARNING "Coverage requested but ${CMAKE_CXX_COMPILER_ID} is not supported")
return()
endif()
target_compile_options(${target_name} ${visibility} --coverage -fprofile-arcs -ftest-coverage)
target_link_options(${target_name} ${visibility} --coverage)
endfunction()

frame_enable_coverage(project_core PUBLIC)
frame_enable_coverage(frame_cli PRIVATE)

set(FRAME_CLAZY_CHECKS "level0" CACHE STRING "Checks passed to clazy-standalone")
find_program(FRAME_CLANG_TIDY_BIN NAMES clang-tidy)
Expand Down Expand Up @@ -109,6 +126,63 @@ add_custom_target(valgrind
VERBATIM
)

find_program(FRAME_CLANG_FORMAT_BIN NAMES clang-format)
if(FRAME_CLANG_FORMAT_BIN)
file(GLOB_RECURSE FRAME_FORMAT_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/*.h
${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tests/*.h
${CMAKE_CURRENT_SOURCE_DIR}/benchmarks/*.cpp
${CMAKE_CURRENT_SOURCE_DIR}/benchmarks/*.h
)

add_custom_target(format
COMMAND ${FRAME_CLANG_FORMAT_BIN} -i ${FRAME_FORMAT_SOURCES}
COMMENT "Running clang-format on repo-owned sources"
VERBATIM
)

add_custom_target(format-check
COMMAND ${FRAME_CLANG_FORMAT_BIN} --dry-run --Werror ${FRAME_FORMAT_SOURCES}
COMMENT "Checking clang-format compliance"
USES_TERMINAL
VERBATIM
)
else()
message(STATUS "clang-format not found; format and format-check targets will be unavailable")
endif()

find_program(FRAME_CPPCHECK_BIN NAMES cppcheck)
add_custom_target(cppcheck
COMMAND ${CMAKE_COMMAND}
-DTOOL_BIN=${FRAME_CPPCHECK_BIN}
-DTOOL_NAME=cppcheck
-DBUILD_DIR=${CMAKE_BINARY_DIR}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/run-cppcheck.cmake
COMMENT "Running cppcheck analysis"
USES_TERMINAL
VERBATIM
)
add_dependencies(lint cppcheck)

if(FRAME_ENABLE_COVERAGE)
find_program(FRAME_GCOVR_BIN NAMES gcovr)
add_custom_target(coverage
COMMAND ${CMAKE_CTEST_COMMAND} --test-dir ${CMAKE_BINARY_DIR} --output-on-failure
COMMAND ${CMAKE_COMMAND} -E echo "---"
COMMAND ${CMAKE_COMMAND} -E echo "Collecting coverage data..."
COMMAND ${FRAME_GCOVR_BIN}
--root ${CMAKE_CURRENT_SOURCE_DIR}
--filter ${CMAKE_CURRENT_SOURCE_DIR}/src/
--print-summary
--html-details ${CMAKE_BINARY_DIR}/coverage/index.html
COMMENT "Running tests and collecting coverage"
USES_TERMINAL
VERBATIM
)
endif()

if(DOXYGEN_FOUND)
set(FRAME_DOXYGEN_OUTPUT_DIR "${CMAKE_BINARY_DIR}/docs/doxygen")
set(FRAME_DOXYGEN_CONFIG "${CMAKE_BINARY_DIR}/Doxyfile")
Expand Down Expand Up @@ -137,3 +211,7 @@ install(FILES contrib/org.example.project.desktop DESTINATION ${CMAKE_INSTALL_DA
if(BUILD_TESTING)
add_subdirectory(tests)
endif()

if(FRAME_ENABLE_BENCHMARKS)
add_subdirectory(benchmarks)
endif()
Loading
Loading