From automation
Applies Sun Lab C++ coding conventions when writing, reviewing, or refactoring code. Covers .h, .hpp, and .cpp files, Doxygen documentation, naming, formatting, error handling, include directives, file ordering, template patterns, embedded (Arduino/PlatformIO) conventions, and Python C++ extension (nanobind/scikit-build-core) conventions. Use when writing new C++ code, modifying existing code, reviewing pull requests, or when the user asks about C++ coding standards.
npx claudepluginhub sun-lab-nbb/ataraxis --plugin automationThis skill uses the workspace's default tool permissions.
Applies Sun Lab C++ coding conventions.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
Applies Sun Lab C++ coding conventions.
You MUST read this skill and load the relevant reference files before writing or modifying C++ code. You MUST verify your changes against the checklist before submitting.
Covers:
Does not cover:
/readme-style)/commit)/skill-design)/explore-codebase)You MUST follow these steps when this skill is invoked.
Read this entire file. The core conventions below apply to ALL C++ code.
Based on the task, load the appropriate reference files:
| Task | Reference to load |
|---|---|
| Writing or modifying Doxygen docs / types | doxygen-and-types.md |
| Writing classes, templates, enums, or structs | class-patterns.md |
| Using Arduino/PlatformIO, clang tools, tests | libraries-and-tools.md |
| Using nanobind extensions, CMake, GIL | libraries-and-tools.md |
| Deploying or verifying tool config files | assets/ directory |
| Reviewing code before submission | anti-patterns.md |
Load multiple references when the task spans multiple domains.
Write or modify C++ code following all conventions from this file and the loaded references.
Complete the verification checklist at the end of this file. Every item must pass before submitting work. For anti-pattern examples, load anti-patterns.md.
Sun Lab projects span Python, C++, and C#. These conventions maximize visual and structural consistency across languages while respecting each language's idiomatic standards.
Shared across all languages:
_snake_case in C++, _camelCase in C#)C++-specific divergences from Python:
get_/set_ snake_case (not bare snake_case as in Python)kPascalCase prefix (not _UPPER_SNAKE_CASE as in Python)kPascalCase prefix (not UPPER_SNAKE_CASE as in Python)@tags (not Google-style docstrings)console.error())C++-specific divergences from C#:
_snake_case (not _camelCase as in C#)kPascalCase prefix (not bare PascalCase as in C#)kPascalCase prefix (not bare PascalCase as in C#)Sun Lab C++ code falls into two paradigms with shared style but different constraints:
All naming, documentation, formatting, and tooling conventions apply identically to both. The verification checklist marks items that apply to only one paradigm.
Use full words, not abbreviations:
| Avoid | Prefer |
|---|---|
pos, idx | position, index |
msg, val | message, value |
buf, cnt | buffer, count |
cb, sz | callback, size |
| Element | Convention | Example |
|---|---|---|
| Classes | PascalCase | TransportLayer, EncoderModule, COBSProcessor |
| Methods | PascalCase | SendData, ReceiveData, SetupModule |
| Accessors | get_/set_ snake_case | get_buffer_size, set_baud_rate |
| Private members | _snake_case | _port, _cobs_processor, _custom_parameters |
| Local variables | snake_case | start_index, payload_size, new_motion |
| Parameters | snake_case | module_type, module_id, baud_rate |
| Constants | kPascalCase | kTimeout, kSerialBufferSize, kCalibrationDelay |
| Enum types | kPascalCase | kCustomStatusCodes, kModuleCommands |
| Enum values | kPascalCase | kStandby, kRotatedCW, kOpen |
| Template type params | PascalCase | PolynomialType, BufferType |
| Template value params | kPascalCase | kPinA, kMaximumTransmittedPayloadSize |
| Namespaces | snake_case | axtlmc_shared_assets, axmc_communication_assets |
| Struct members | snake_case | module_type, pulse_duration, return_code |
| Macros | UPPER_SNAKE | PACKED_STRUCT, ENCODER_USE_INTERRUPTS |
SendData, ResetTransmissionBuffer, ReadEncoderProcess, Handle, DoSomethingAccessor methods use get_/set_ snake_case to visually distinguish trivial field access from
methods that perform real work. This follows the Google C++ naming convention:
/// Returns the size of the instance's transmission buffer, in bytes.
[[nodiscard]]
static constexpr uint16_t get_transmission_buffer_size()
{
return kTransmissionBufferSize;
}
/// Returns the size of the payload currently stored in the instance's transmission buffer.
[[nodiscard]]
uint8_t get_bytes_in_transmission_buffer() const
{
return _transmission_buffer[kBufferLayout::kPayloadSizeIndex];
}
At the call site, casing signals intent: PascalCase means work is being done, get_/set_
snake_case means simple field access.
Use static constexpr with the kPascalCase prefix and a Doxygen comment:
/// Stores the minimum number of bytes required to form a valid packet.
static constexpr uint16_t kMinimumPacketSize = 5;
For constants derived from template parameters, add the // NOLINT(*-dynamic-static-initializers)
suppression when clang-tidy reports a false positive:
static constexpr int32_t kMultiplier = kInvertDirection ? -1 : 1; // NOLINT(*-dynamic-static-initializers)
Prefer clarity to brevity. For functions with multiple parameters of the same type or boolean parameters, use inline comments to label arguments:
// Good - labeled arguments clarify meaning
SendData(
static_cast<uint8_t>(kCustomStatusCodes::kRotatedCW), // status_code
delta // value
);
// Acceptable - single argument or meaning obvious
CompleteCommand();
Embedded microcontrollers prohibit exceptions, RTTI, and dynamic allocation. Use status codes and boolean returns:
bool RunActiveCommand() override
{
switch (static_cast<kModuleCommands>(GetActiveCommand()))
{
case kModuleCommands::kCheckState: CheckState(); return true;
default: return false;
}
}
Extension code may throw exceptions for error propagation to Python. nanobind automatically translates C++ exceptions to Python exceptions:
throw std::invalid_argument("Unsupported precision. Use 'ns', 'us', 'ms', or 's'.");
Use static_assert for compile-time constraint checking on template parameters:
static_assert(kPinA != kPinB, "EncoderModule PinA and PinB cannot be the same!");
Use a structured format: context ("Unable to..."), constraint ("must be..."), actual value ("but received..."). For runtime errors, include the actual value when available.
// Resets the overflow tracker. The overflow accumulates insignificant motion between reporting
// cycles to filter sensor noise while preserving real displacement.
_overflow = 0;
// Set x to 5 before x = 5)// ====== or // ------)@code / @endcode example blocks in Doxygen documentation. Examples go stale
as APIs evolve and create maintenance debt. Keep documentation concise — the @brief, @param,
and @returns tags are sufficient. This parallels the Python convention of not including
Examples sections in docstringsUse #ifndef / #define / #endif with a library-prefixed identifier:
#ifndef AXTLMC_TRANSPORT_LAYER_H
#define AXTLMC_TRANSPORT_LAYER_H
// ... file contents ...
#endif //AXTLMC_TRANSPORT_LAYER_H
The guard identifier follows the pattern: LIBRARY_PREFIX_FILE_NAME_H. Use the library's
abbreviated prefix (e.g., AXTLMC for ataraxis-transport-layer-mc, AXMC for
ataraxis-micro-controller).
clang-format enforces include sorting (SortIncludes: CaseSensitive). The conventional order is:
<Arduino.h><Encoder.h>, <digitalWriteFast.h><transport_layer.h>, <module.h>, <kernel.h>"encoder_module.h", "valve_module.h"All includes must be at the top of the file. Include sorting is enforced by clang-format — do
not manually reorder. Use angle brackets (<header.h>) for library headers and quotes
("header.h") for local project headers.
All definitions within a file follow this vertical ordering from top to bottom:
@file and @brief)#ifndef / #define)#define ENCODER_USE_INTERRUPTS)using namespace — allowed in header-only libraries; see below)static constexpr at file scope)enum class definitions)static_assert statements (compile-time validation)
b. Public nested enums
c. Public constructors and destructors
d. Public methods (virtual overrides first, then non-virtual)
e. Public destructor (~ClassName() override = default)
f. Private nested structs
g. Private constants (static constexpr)
h. Private member variables
i. Private methodsWithin a class, order by visibility: public first, then private. Always write access
modifiers explicitly.
Each .h file should contain exactly one primary class. The file name must use snake_case and
match the class name converted to snake_case (e.g., transport_layer.h contains
TransportLayer). Shared asset namespaces with enums and structs may be in a single file (e.g.,
axtlmc_shared_assets.h).
Prefer early returns (guard clauses) over deeply nested conditionals:
void SendPulse()
{
if (!kOutput)
{
AbortCommand();
return;
}
// Main logic at minimal indentation level.
Pulse();
}
Classes must keep all data members private (_snake_case) and expose them through
get_/set_ accessors when external access is needed. This enforces encapsulation and keeps
the class interface explicit.
Structs may use public data members (snake_case) for passive data holders that have no
invariants or methods beyond simple initialization. Packed structs used for binary serialization
are the primary example.
// Class — all data members private, accessed via getters
class TransportLayer
{
public:
[[nodiscard]] uint8_t get_runtime_status() const { return _runtime_status; }
private:
uint8_t _runtime_status = 0;
};
// Struct — public members allowed for passive data
struct CustomRuntimeParameters
{
uint32_t pulse_duration = 35000; ///< The time, in microseconds, to keep the valve open.
} PACKED_STRUCT;
SeparateDefinitionBlocks: Always)public:, private:)ColumnLimit: 120).clang-format).clang-tidy, WarningsAsErrors: '*')int* pointer, int& reference)Consecutive assignments and macros are aligned for readability:
_custom_parameters.report_CCW = true;
_custom_parameters.report_CW = true;
_custom_parameters.delta_threshold = 15;
Template declarations always appear on a separate line:
template <const uint8_t kPinA, const uint8_t kPinB, const bool kInvertDirection = false>
class EncoderModule final : public Module
Short case labels and simple if/else statements may appear on a single line (enforced by
clang-format AllowShortCaseLabelsOnASingleLine: true):
case kModuleCommands::kCheckState: CheckState(); return true;
case kModuleCommands::kReset: ResetEncoder(); return true;
default: return false;
if (kTonePin == 255) _custom_parameters.tone_duration = 0;
Canonical configs are stored in assets/. When working in a C++ project, verify that
.clang-format and .clang-tidy in the project root match the canonical versions.
.clang-format: assets/embedded/.clang-format.clang-format: assets/extension/.clang-format.clang-tidy: assets/.clang-tidyThe two .clang-format variants differ only in AccessModifierOffset (0 vs -2) and
IndentAccessModifiers (true vs false). All other settings are identical. The .clang-tidy
configuration is shared across both archetypes.
| Skill | Relationship |
|---|---|
/python-style | Provides Python conventions; C++ conventions parallel these |
/csharp-style | Provides C# conventions; C++ conventions parallel these |
/readme-style | Provides README conventions; invoke for README tasks |
/commit | Provides commit message conventions; invoke for commit tasks |
/skill-design | Provides skill file conventions; invoke for skill authoring tasks |
/explore-codebase | Provides project context that informs style-compliant code changes |
/api-docs | Provides Doxygen/Breathe API documentation build conventions |
When reviewing or modifying C++ code, proactively check for style violations and fix them. When writing new code, apply all conventions from this skill and its references without being asked. If you notice existing code near your changes that violates conventions, mention it to the user but do not fix it unless asked.
You MUST verify your edits against this checklist before submitting any changes to C++ files.
C++ Style Compliance:
- [ ] Doxygen documentation on all public and private members
- [ ] @brief tags use third-person imperative mood ("Provides..." not "This class provides...")
- [ ] Boolean members documented with "Determines whether..."
- [ ] File-level Doxygen comment with @file and @brief present
- [ ] Doxygen tag order: @brief -> @details -> @warning/@note -> @tparam -> @param -> @returns
- [ ] All lines <= 120 characters
- [ ] 4-space indentation, no tabs
- [ ] Allman brace style (opening braces on new lines)
- [ ] Full words used (no abbreviations like pos, idx, val, buf)
- [ ] Classes use PascalCase
- [ ] Methods use PascalCase (both public and private)
- [ ] Accessors use get_/set_ snake_case (not PascalCase)
- [ ] Class data members are private (_snake_case) with get_/set_ accessors
- [ ] Struct data members may be public (snake_case) for passive data holders
- [ ] Private members use _snake_case prefix
- [ ] Local variables and parameters use snake_case
- [ ] Constants use kPascalCase prefix (static constexpr)
- [ ] Enum types and values use kPascalCase prefix
- [ ] Template type params use PascalCase; value params use kPascalCase
- [ ] Namespaces use snake_case
- [ ] Macros use UPPER_SNAKE_CASE
- [ ] Include guards use LIBRARY_PREFIX_FILE_NAME_H pattern
- [ ] Include sorting delegated to clang-format (do not manually reorder)
- [ ] Pointer/reference alignment is left (int* pointer, int& reference)
- [ ] Consecutive assignments aligned (AlignConsecutiveAssignments)
- [ ] Template declarations on separate lines
- [ ] Guard clauses / early returns preferred over deep nesting
- [ ] One primary class per file; file name matches class in snake_case
- [ ] Public members above private members in class definition
- [ ] Inline comments use third person imperative
- [ ] No heavy section separator blocks (// ====== or // ------)
- [ ] No @code/@endcode example blocks in Doxygen documentation
- [ ] clang-format applied before commit
- [ ] clang-tidy passes with zero warnings
Embedded-Specific Compliance (skip for extension projects):
- [ ] No exceptions (use status codes and boolean returns)
- [ ] No dynamic memory allocation (no new/delete, no STL containers with heap allocation)
- [ ] No RTTI (no dynamic_cast, no typeid)
- [ ] static_assert used for compile-time template parameter validation
- [ ] Scoped enums (enum class) with explicit backing type (uint8_t)
- [ ] Structs use PACKED_STRUCT macro for binary serialization
- [ ] explicit keyword on single-argument constructors
- [ ] [[nodiscard]] on const getter methods
- [ ] virtual destructors use = default on leaf and base classes
- [ ] final keyword on leaf classes that should not be subclassed
- [ ] static constexpr for compile-time constants (not #define)
- [ ] NOLINT comments for legitimate clang-tidy false positives only
Extension-Specific Compliance (skip for embedded projects):
- [ ] NB_MODULE binding block at end of file with NOLINTNEXTLINE suppression
- [ ] GIL released during blocking operations (nb::gil_scoped_release)
- [ ] CMakeLists.txt uses nanobind_add_module with NB_STATIC
- [ ] Extension class prefixed with C (e.g., CPrecisionTimer) to distinguish from Python wrapper