chore: add pi skills for tdd, cmake, and git

Create .pi/skills/ with three skills:
- tdd: Red-Green-Refactor workflow, Unity assertions, CMock patterns
- cmake: build commands, adding modules, dependency FetchContent pattern
- git: conventional commits, semver tagging, branching conventions

Update AGENTS.md and README.md to reference the skills and template
project description.
This commit is contained in:
2026-06-15 04:27:39 +02:00
parent 286c51b2e7
commit 79d3b92511
6 changed files with 623 additions and 77 deletions
+43
View File
@@ -0,0 +1,43 @@
# Pi Skills
Project-level skills loaded by Pi on-demand.
## Skills
| Skill | Description |
| ------------------------------ | ---------------------------------------------------- |
| [tdd](skills/tdd/SKILL.md) | TDD workflow with Unity and CMock |
| [cmake](skills/cmake/SKILL.md) | Build configuration, adding modules and dependencies |
| [git](skills/git/SKILL.md) | Commit messages, tagging, and branching workflow |
## Installation
Skills are auto-discovered by Pi from `.pi/skills/`. No extra setup
required.
To install this project's skills into another project:
```sh
pi install -l git:github.com/portersky/helloctdd
```
Or as a temporary package for the current session:
```sh
pi -e git:github.com/portersky/helloctdd
```
Pi auto-discovers `skills/` directories from installed packages.
## Adding a New Skill
Place a `SKILL.md` inside a directory under `.pi/skills/`:
```
.pi/skills/
my-skill/
SKILL.md
```
Pi validates the frontmatter and registers `/skill:my-skill` in the
TUI.
+145
View File
@@ -0,0 +1,145 @@
---
name: cmake
description: CMake build configuration for the ctdd template project. Use when
adding modules, adding dependencies, configuring coverage or sanitizers, or
understanding the build system.
---
# CMake Skill
## Build Commands
| Action | Command |
| --------- | ------------------------------------------------------------------------------------------ |
| Configure | `cmake -S . -B build -G Ninja` |
| Build | `ninja -C build` |
| Run tests | `ninja -C build check` |
| Coverage | `cmake -S . -B build-cov -G Ninja -DENABLE_COVERAGE=ON` then `ninja -C build-cov coverage` |
| ASan | `cmake -S . -B build-asan -G Ninja -DENABLE_ASAN=ON` then `ninja -C build-asan` |
> ASan and coverage cannot be used together.
## Adding a Module
Modules live in `ctdd/` as static libraries. Register them in
`ctdd/CMakeLists.txt`:
```cmake
add_library(ctdd_<module> <module>.c)
target_include_directories(ctdd_<module> PUBLIC "${CMAKE_SOURCE_DIR}")
target_compile_features(ctdd_<module> PRIVATE c_std_23)
```
Link into `main` in the root `CMakeLists.txt`:
```cmake
target_link_libraries(main PRIVATE ... ctdd_<module>)
```
## Adding a Dependency
Dependencies are fetched via custom `Find<name>.cmake` scripts in `deps/`.
`deps/` is on `CMAKE_MODULE_PATH` so `find_package()` resolves to these
scripts first.
### 1. Create `deps/Find<name>.cmake`
Pattern (follow `deps/FindUnity.cmake` or `deps/FindCMock.cmake`):
```cmake
if (DEFINED _FIND<NAME>_INCLUDED)
return()
endif()
set(_FIND<NAME>_INCLUDED TRUE)
set(<NAME>_VERSION "x.y.z")
message(STATUS "Fetching <name> ${<NAME>_VERSION}")
include(FetchContent)
FetchContent_Declare(
<name>
URL https://github.com/org/<name>/archive/refs/tags/v${<NAME>_VERSION}.zip
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
)
FetchContent_MakeAvailable(<name>)
if (NOT TARGET <Name>::<Name>)
add_library(<Name>::<Name> ALIAS <name>)
endif()
set(<Name>_FOUND TRUE)
```
For libraries without CMakeLists.txt (like CMock), use
`FetchContent_Populate` and compile manually. See `FindCMock.cmake`
for the pattern.
### 2. Add `find_package(<Name> REQUIRED)` to `CMakeLists.txt`
Place it after `include(Platform)` and `include(Flags)`.
### 3. Link with `<Name>::<Name>`
In `ctdd/CMakeLists.txt` or `tests/CMakeLists.txt` as needed:
```cmake
target_link_libraries(ctdd_<module> PRIVATE <Name>::<Name>)
```
## Platform Flags
`deps/Platform.cmake` sets these variables for conditional logic:
| Variable | Meaning |
| ----------------- | ------------------------------------------------ |
| `IS_CLANG_OR_GCC` | Clang or GCC compiler |
| `IS_MSVC` | MSVC compiler |
| `IS_WINDOWS` | Windows target |
| `IS_LINUX` | Linux target |
| `IS_MACOS` | macOS target |
| `IS_IOS` | iOS target |
| `IS_ANDROID` | Android target |
| `IS_EMSCRIPTEN` | Emscripten/WASM target |
| `ARCH` | Normalized arch (`amd64`, `arm64`, `wasm`, etc.) |
| `IS_AMD64` | x86_64 |
| `IS_ARM64` | arm64/aarch64 |
## Compiler Flags
`deps/Flags.cmake` defines `BASE_OPTIONS` (warning flags) and
`BASE_DEFINITIONS`. Apply per-target to avoid polluting fetched
dependencies:
```cmake
target_compile_options(<target> PRIVATE ${BASE_OPTIONS})
target_compile_definitions(<target> PRIVATE ${BASE_DEFINITIONS})
```
## CMock Mock Generation
`tests/CMakeLists.txt` provides a `cmock_generate_mock` helper function:
```cmake
cmock_generate_mock(<target> "<absolute-path-to-header>")
```
With optional config file:
```cmake
cmock_generate_mock(<target> "<header>" "cmock_config.yml")
```
Generated mocks go into `build/mocks/` as `Mock<name>.h/.c`.
## Coverage
Enabled via `-DENABLE_COVERAGE=ON`. Requires GCC or Clang + `gcovr`.
Report at `build-cov/coverage/index.html`. Only `ctdd/` sources are
measured; Unity, CMock, and mocks are excluded.
## Sanitizers
`deps/Sanitizers.cmake` provides AddressSanitizer via `-DENABLE_ASAN=ON`.
Incompatible with coverage.
+167
View File
@@ -0,0 +1,167 @@
---
name: git
description: Git workflow for the ctdd template project. Use when committing
changes, creating tags for releases, or following the branching and commit
message conventions.
---
# Git Skill
## Commit Messages
Follow [Conventional Commits](https://www.conventionalcommits.org/) with the
50/72 rule.
### Format
```
<type>: <subject>
<body>
<footer>
```
- **Subject:** max 50 characters, imperative mood, no period
- **Body:** wrapped at 72 characters, optional
- **Blank line** between subject and body
- **No co-author trailers** (`Co-Authored-By:` is forbidden)
- **No em dashes** (`—`). Use a colon or rewrite the sentence
### Types
| Type | Use |
| ---------- | ---------------------------------- |
| `feat` | New feature or module |
| `fix` | Bug fix |
| `docs` | Documentation only |
| `chore` | Build, deps, config, housekeeping |
| `ci` | CI/CD changes |
| `refactor` | Code change without feature or fix |
| `test` | Adding or changing tests |
| `style` | Formatting, whitespace, semicolons |
### Examples
Good:
```
feat: add stopwatch timer
Replace Hello World with a live stopwatch that prints elapsed time
in HH:MM:SS.mmm format, updating every 10ms with color output.
```
```
fix: clear Unity INTERFACE_SYSTEM_INCLUDE_DIRECTORIES
CMake rejects the path inside the build tree on newer versions.
```
```
chore: bump CMock to v2.6.0
```
Bad:
```
added a new thing # no type, lowercase, vague
feat: add stopwatch # no body for a significant change
feat: add stopwatch timer. # trailing period
feat: add timer — it's fast # em dash not allowed
```
## Committing
```sh
git add -A
git commit -m "type: subject"
```
For multi-line messages:
```sh
git commit -m "type: subject
body line 1
body line 2"
```
## Tagging
This project uses semantic versioning for template releases.
### Creating a tag
```sh
git tag -a v0.1.0 -m "Release v0.1.0"
git push origin v0.1.0
```
Tag messages follow the same rules as commit messages: no em dashes,
wrapped at 72 characters.
### Tag naming
| Version | Meaning |
| -------- | ------------------------------------ |
| `v0.x.0` | Minor changes, breaking template API |
| `v0.x.y` | Patch changes, backward-compatible |
| `v1.x.0` | Major release |
### When to tag
- After merging a feature branch that changes the template structure
- Before sharing the template with others
- When CMakeLists.txt, deps/, or build system changes could affect
projects using this template
### Listing tags
```sh
git tag -l
```
### Deleting a tag
```sh
git tag -d v0.1.0
git push origin --delete v0.1.0
```
## Branching
For local development:
```sh
git checkout -b feat/add-counter
```
Branch naming:
| Prefix | Use |
| -------- | ------------- |
| `feat/` | New feature |
| `fix/` | Bug fix |
| `chore/` | Housekeeping |
| `docs/` | Documentation |
## Common Commands
| Command | Use |
| ----------------------- | --------------------------------- |
| `git log --oneline -10` | Recent history |
| `git diff --staged` | Check staged changes |
| `git status` | Current state |
| `git reset HEAD~1` | Undo last commit (keep changes) |
| `git rebase -i HEAD~3` | Interactive rebase last 3 commits |
## Checklist Before Committing
- [ ] Tests pass: `ninja -C build check`
- [ ] Commit message follows 50/72 rule
- [ ] Conventional commit type used
- [ ] No co-author trailers
- [ ] No em dashes
- [ ] No large unchanged regions reformatted
- [ ] No speculative code or unused imports
+223
View File
@@ -0,0 +1,223 @@
---
name: tdd
description: Test-driven development with Unity and CMock. Use when adding new
modules, writing tests, mocking dependencies, or following Red-Green-Refactor
in this C23 project.
---
# TDD Skill
## Quick Reference
| Action | Command |
| ----------------------- | ------------------------------------------------------- |
| Configure (default) | `cmake -S . -B build -G Ninja` |
| Configure (coverage) | `cmake -S . -B build-cov -G Ninja -DENABLE_COVERAGE=ON` |
| Build | `ninja -C build` |
| Run tests (full output) | `ninja -C build check` |
| Run tests (summary) | `ninja -C build test` |
| Run tests + coverage | `ninja -C build-cov coverage` |
## Red-Green-Refactor
Every change to `ctdd/` follows this cycle:
1. **RED** — Write a failing test. Confirm it fails.
2. **GREEN** — Write the minimum code to pass. Confirm it passes.
3. **REFACTOR** — Clean up without changing behavior. Tests still pass.
Never skip the RED step. If the test passes immediately, you wrote the test
before the implementation was missing.
## Adding a New Module
### 1. Write the header
Create `ctdd/<module>.h` with the public prototype(s).
```c
#pragma once
#include <stddef.h>
auto fn_name(int arg) -> int;
```
### 2. Write the test
Create `tests/test_<module>.c`. Two patterns exist:
**State-based** (pure functions, no mocks):
```c
#include "unity.h"
#include "ctdd/<module>.h"
void setUp(void) {}
void tearDown(void) {}
void test_fn_name_returns_expected(void) {
TEST_ASSERT_EQUAL_INT(42, fn_name(1));
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_fn_name_returns_expected);
return UNITY_END();
}
```
**Interaction-based** (mocking a dependency):
```c
#include "unity.h"
#include "ctdd/<module>.h"
#include "Mock<dep>.h"
void setUp(void) { Mock<dep>_Init(); }
void tearDown(void) { Mock<dep>_Verify(); Mock<dep>_Destroy(); }
void test_fn_calls_dep(void) {
dep_fn_ExpectAndReturn(arg, expected);
TEST_ASSERT_TRUE(fn_name());
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_fn_calls_dep);
return UNITY_END();
}
```
### 3. Register the test in `tests/CMakeLists.txt`
Add after existing test targets, before the `check` target:
**State-based (no mock):**
```cmake
add_executable(test_<module> test_<module>.c)
target_include_directories(test_<module> PRIVATE "${CMAKE_SOURCE_DIR}")
target_link_libraries(test_<module> PRIVATE ctdd_<module> Unity::Unity)
target_compile_features(test_<module> PRIVATE c_std_23)
add_test(NAME test_<module> COMMAND test_<module>)
list(APPEND TEST_TARGETS test_<module>)
```
**Interaction-based (with mock):**
```cmake
add_executable(test_<module> test_<module>.c)
target_include_directories(test_<module> PRIVATE "${CMAKE_SOURCE_DIR}")
target_link_libraries(test_<module> PRIVATE ctdd_<module> Unity::Unity CMock::CMock)
target_compile_features(test_<module> PRIVATE c_std_23)
cmock_generate_mock(test_<module> "${CMAKE_SOURCE_DIR}/ctdd/<dep>.h")
add_test(NAME test_<module> COMMAND test_<module>)
list(APPEND TEST_TARGETS test_<module>)
```
### 4. Stub the implementation
Create `ctdd/<module>.c` with a dummy return:
```c
#include "ctdd/<module>.h"
auto fn_name(int arg) -> int {
return 0;
}
```
Register the library in `ctdd/CMakeLists.txt`:
```cmake
add_library(ctdd_<module> <module>.c)
target_include_directories(ctdd_<module> PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
target_compile_features(ctdd_<module> PRIVATE c_std_23)
```
### 5. Confirm RED
```sh
ninja -C build check
```
The test must fail. If it does not, something is wrong.
### 6. Implement and confirm GREEN
Write the real implementation. Run tests again:
```sh
ninja -C build check
```
All tests must pass.
## CMock Patterns
### Expecting a call with arguments
```c
dep_fn_Expect(arg1, arg2);
```
### Expecting a call and returning a value
```c
dep_fn_ExpectAndReturn(arg, expected_value);
```
### Verifying a call was NOT made
Do not set any `_Expect`. If the function is called, CMock fails
automatically in `tearDown`.
### Multiple calls
```c
dep_fn_ExpectAndReturn(first_call_arg, first_return);
dep_fn_ExpectAndReturn(second_call_arg, second_return);
```
Calls are matched in order.
## Test Naming
- `test_<function>_<scenario>`
- Use present tense: `test_upper_converts_lowercase`
- Cover: happy path, edge cases, boundaries, and error conditions
## Unity Assertions
| Assertion | Use |
| -------------------------------- | ---------------- |
| `TEST_ASSERT_TRUE(x)` | Boolean true |
| `TEST_ASSERT_FALSE(x)` | Boolean false |
| `TEST_ASSERT_EQUAL_INT(a, b)` | Integer equality |
| `TEST_ASSERT_EQUAL_STRING(a, b)` | String equality |
| `TEST_ASSERT_NULL(p)` | Null pointer |
| `TEST_ASSERT_NOT_NULL(p)` | Non-null pointer |
## Coverage
To check coverage after tests:
```sh
ninja -C build-cov coverage
```
Open `build-cov/coverage/index.html` in a browser.
## Checklist
Before considering a task done:
- [ ] Header declares the public API
- [ ] Test covers at least one happy path, one edge case
- [ ] Test registered in `tests/CMakeLists.txt`
- [ ] Library registered in `ctdd/CMakeLists.txt` (if new module)
- [ ] `ninja -C build check` passes with zero failures
- [ ] No semicolons after closing braces
- [ ] Trailing return type on all functions
- [ ] East const (`char const*`)
- [ ] `snake_case` naming
+42 -75
View File
@@ -2,8 +2,18 @@
## Project Overview
`ctdd` is a C23 project wired for test-driven development using
Unity and CMock.
`ctdd` is a C23 template project wired for test-driven development
using Unity and CMock. Use it as the core template when scaffolding
new projects.
For TDD workflows (adding modules, writing tests, mocking), load the
`tdd` skill with `/skill:tdd`.
For build configuration (dependencies, coverage, sanitizers), load the
`cmake` skill with `/skill:cmake`.
For committing, tagging, and branching, load the `git` skill with
`/skill:git`.
## Build System
@@ -72,31 +82,40 @@ to the custom scripts instead of system-installed packages.
## Coding Conventions
### C
- **Language:** C23
- **Trailing return type** for function signatures (e.g. `auto fn() -> void`)
- **4-space indentation**
- **No semicolons after closing braces** for namespaces/classes
- `auto` for obvious types (e.g. `auto main(...) -> int`)
- **East const** (e.g. `char const*` not `const char*`)
- **4-space indentation**
- `<>` includes for system headers (C stdlib, OS, etc.)
- `""` includes for third-party and local headers
- **Naming:** `snake_case` for variables, functions, and structs
- **Naming:** `SCREAMING_SNAKE_CASE` only for macros and constants
### C++
- **Trailing return type** for function signatures
(e.g. `auto fn() -> void`)
- `auto` for obvious types (e.g. `auto main(...) -> int`)
- **No semicolons after closing braces** for namespaces/classes
- **Trailing return type** for all function definitions, including
operators (e.g. `auto operator=(T&&) noexcept -> T&`)
- **Public members first** in class declarations, private members at the
bottom
- `<>` includes only for system headers (std, OS, etc.)
- `""` includes for third-party dependencies (e.g. `fmt`,
`nlohmann/json`)
- **Naming:** `snake_case` for variables, functions, and classes
- **Naming:** `SCREAMING_SNAKE_CASE` only for macros and constants
- Include order:
1. C++ standard library headers (`<chrono>`, `<vector>`, etc.)
2. *(blank line)*
3. C standard library headers (`<stdlib.h>`, `<string.h>`, etc.)
4. *(blank line)*
5. OS-specific headers (Windows API, POSIX, etc.)
6. *(blank line)*
7. Third-party dependencies (`"fmt/core.h"`, etc.)
8. *(blank line)*
9. Local/project headers
- **Public members first** in class declarations, private members at
the bottom
### Include Order
Both C and C++ follow the same include order:
1. C++ standard library headers (`<chrono>`, `<vector>`, etc.)
2. *(blank line)*
3. C standard library headers (`<stdlib.h>`, `<string.h>`, etc.)
4. *(blank line)*
5. OS-specific headers (Windows API, POSIX, etc.)
6. *(blank line)*
7. Third-party dependencies (`"fmt/core.h"`, etc.)
8. *(blank line)*
9. Local/project headers
## Shell Scripts
@@ -164,53 +183,6 @@ deps/
FindCMock.cmake Fetches CMock v2.6.0 via ZIP
```
## TDD Workflow
This project follows Red-Green-Refactor. All changes to testable source
files under `ctdd/` should be test-driven: write a failing test first,
then implement.
### Adding a new module
1. Create `ctdd/<module>.h` with the public prototype.
2. Create `tests/test_<module>.c`. Set CMock expectations for any
dependency calls, then assert the result.
3. Register the test in `tests/CMakeLists.txt`:
```cmake
add_executable(test_module test_module.c)
target_include_directories(test_module PRIVATE "${CMAKE_SOURCE_DIR}")
target_link_libraries(test_module PRIVATE ctdd_module Unity::Unity CMock::CMock)
target_compile_features(test_module PRIVATE c_std_23)
cmock_generate_mock(test_module "${CMAKE_SOURCE_DIR}/ctdd/dep.h")
add_test(NAME test_module COMMAND test_module)
list(APPEND TEST_TARGETS test_module)
```
4. Stub `ctdd/<module>.c` with a dummy return, confirm RED, implement,
confirm GREEN:
```sh
ninja -C build check
```
### Mocking a dependency
Use `cmock_generate_mock` in the test target to generate a mock from a
header. Include `Mock<name>.h` in the test and use the generated API:
```c
#include "Mockdep.h"
void setUp(void) { Mockdep_Init(); }
void tearDown(void) { Mockdep_Verify(); Mockdep_Destroy(); }
void test_something(void) {
dep_fn_ExpectAndReturn(arg, expected);
TEST_ASSERT_TRUE(module_do_thing());
}
```
## Behavioral Guidelines
Reduce common LLM coding mistakes. Bias toward caution over speed.
@@ -247,8 +219,3 @@ Transform tasks into verifiable goals:
- "Add validation" means: write tests for invalid inputs, then pass.
- "Fix the bug" means: write a test that reproduces it, then pass.
- "Refactor X" means: ensure tests pass before and after.
## Platform Support
The project supports Windows, Linux, macOS, Emscripten, and Android via
`Platform.cmake` and `Flags.cmake` in `deps/`.
+3 -2
View File
@@ -1,8 +1,9 @@
# ctdd
A C23 project wired for test-driven development using
A C23 template project wired for test-driven development using
[Unity](https://github.com/ThrowTheSwitch/Unity) and
[CMock](https://github.com/ThrowTheSwitch/CMock).
[CMock](https://github.com/ThrowTheSwitch/CMock). Use it as the core
template when scaffolding new projects.
All dependencies are fetched automatically via CMake `FetchContent` with no
manual installation required beyond the tools listed below.