添加AGENTS SKILL
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
---
|
||||
name: structive-development
|
||||
description: Use when defining, extending, modifying, debugging, or reviewing Structive property schemas, Type_Descriptor, Accessors, Attributes, Constraints, managed read/write, guards, synchronization topology, runtime type-erased access, Presentation/extensions, tests, CMake, install/export, or when integrating a C++ type with Structive.
|
||||
---
|
||||
# Structive Development
|
||||
|
||||
## Goal
|
||||
|
||||
Express structural facts about ordinary C++ objects without replacing the native C++ model. Keep compile-time facts compile-time, keep external policy outside Core, and make managed synchronization pay only for mutable state that actually needs it.
|
||||
|
||||
## Read before editing
|
||||
|
||||
Use these as the source of truth:
|
||||
|
||||
1. `AGENTS.md`.
|
||||
2. `docs/DESIGN.zh-CN.md` or `docs/DESIGN.md` for architecture.
|
||||
3. `docs/CORE_GUIDE.zh-CN.md` for Core API usage.
|
||||
4. `docs/EXTENSIONS.zh-CN.md` for extension ownership.
|
||||
5. The focused header and tests for the feature being changed.
|
||||
|
||||
When Structive is embedded under Adminive, also respect the parent Adminive instructions, but this nested file owns Structive-specific decisions.
|
||||
|
||||
## Define a described object
|
||||
|
||||
Keep fields as ordinary C++ members and inherit `Property_Object<T>` only for managed APIs:
|
||||
|
||||
```cpp
|
||||
struct Device : structive::Property_Object<Device> {
|
||||
double temperature{};
|
||||
int serial_number{};
|
||||
};
|
||||
template <>
|
||||
struct structive::Type_Descriptor<Device> {
|
||||
static auto get() {
|
||||
using namespace structive;
|
||||
return object<Device>(
|
||||
field<&Device::temperature>(key<"temperature">),
|
||||
field<&Device::serial_number>(key<"serial_number">, read_only)
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Do not wrap each member in a new storage type just to make it participate in Structive.
|
||||
|
||||
## Choose the correct mechanism
|
||||
|
||||
| Requirement | Mechanism |
|
||||
|---|---|
|
||||
| Register a stored member | `field<&T::member>(...)` |
|
||||
| Stable runtime/schema name | `key<"name">` |
|
||||
| Narrow intrinsic capability | `read_only` or capability metadata supported by the descriptor |
|
||||
| Unit/sensitive/custom structural metadata | Attribute |
|
||||
| Reusable object-level inheritable metadata | `defaults(...)` with an inheritable Attribute |
|
||||
| Candidate value validity | Constraint + explicit validation helper |
|
||||
| Managed typed read/write | `Property_Object::read` / `write` |
|
||||
| Atomic multi-property consistency | shared synchronization group + typed guard |
|
||||
| Runtime-selected property access | `Property_Object_Base::runtime_read/runtime_write` |
|
||||
| UI/persistence/RPC-specific meaning | Extension-owned Attribute/logic, not Core |
|
||||
|
||||
## Managed access rules
|
||||
|
||||
Prefer typed member/key access in normal C++ code:
|
||||
|
||||
```cpp
|
||||
auto value = device.read<&Device::temperature>();
|
||||
device.write<&Device::temperature>(42.0);
|
||||
```
|
||||
|
||||
Use guards for a consistent multi-property operation:
|
||||
|
||||
```cpp
|
||||
auto guard = device.lock_unique<&Device::minimum, &Device::maximum>();
|
||||
guard.set<&Device::minimum>(20);
|
||||
guard.set<&Device::maximum>(120);
|
||||
```
|
||||
|
||||
Do not replace direct raw C++ access. Raw access intentionally bypasses the managed contract and is the caller's responsibility.
|
||||
|
||||
## Synchronization workflow
|
||||
|
||||
Before changing synchronization code, preserve these invariants:
|
||||
|
||||
- Only intrinsically writable stored properties contribute mutable lock domains.
|
||||
- Stored read-only properties resolve to `unsynchronized_slot` and managed reads do not acquire a lock.
|
||||
- `independent`, `shared`, `unsynchronized`, groups, compile-time member rules, runtime-key rules, and per-instance overrides all resolve through one lock-slot topology.
|
||||
- Multiple locks are acquired in stable order.
|
||||
- `No_Lock_Policy` removes real mutex storage/locking work instead of simulating mutexes.
|
||||
- Computed read-only properties may require a synchronized dependency view when they derive from mutable state; do not give their immutable storage a fake lock.
|
||||
|
||||
Synchronization protects in-process consistency only. Do not add transaction or authorization semantics here.
|
||||
|
||||
## Runtime adapter workflow
|
||||
|
||||
Runtime access is a dynamic bridge. It must keep exact type semantics:
|
||||
|
||||
- Unknown key -> the appropriate not-found result.
|
||||
- Intrinsically unreadable/unwritable -> capability result.
|
||||
- Runtime type mismatch -> type-mismatch result.
|
||||
- Intrinsically writable but not copy-writable -> `unsupported_runtime_write`.
|
||||
- Successful runtime write reuses the managed synchronization path.
|
||||
|
||||
`runtime_write` copies from a `const` type-erased input. Do not steal/move from that pointer to make move-only types appear runtime-copy-writable. Typed `write` remains the path for move-only values.
|
||||
|
||||
## Attribute and Extension workflow
|
||||
|
||||
Core owns the generic Attribute protocol; the domain that defines a category owns its interpretation.
|
||||
|
||||
For presentation metadata, use the extension:
|
||||
|
||||
```cpp
|
||||
field<&Device::temperature>(
|
||||
key<"temperature">,
|
||||
structive::presentation::label<"Temperature">,
|
||||
structive::presentation::description<"Current temperature">,
|
||||
structive::presentation::group<"Environment">,
|
||||
structive::presentation::order<1>
|
||||
)
|
||||
```
|
||||
|
||||
Read it through `presentation::describe<...>(schema)`. Presentation fallback from missing label to property key belongs to the extension, not Core.
|
||||
|
||||
For a new metadata domain, create a category and Attribute in the extension. Do not add an enum/switch to Core merely because an adapter wants a new hint.
|
||||
|
||||
## Schema and compile-time contracts
|
||||
|
||||
Structural mistakes should fail during compilation/configuration whenever they are statically knowable. Existing compile-fail coverage includes duplicate keys, duplicate storage, missing keys, capability/accessor mismatch, incompatible constraints, and foreign synchronization members.
|
||||
|
||||
When adding another compile-time invariant:
|
||||
|
||||
1. Express it in concepts/`requires`/`static_assert` at the schema boundary.
|
||||
2. Add a source under `core/tests/compile_fail/`.
|
||||
3. Register it with the existing `structive_expect_compile_failure` mechanism.
|
||||
4. Do not replace compile-time rejection with a runtime error for typed APIs.
|
||||
|
||||
Empty schemas are valid. Do not reintroduce pack/CTAD assumptions that require at least one property unless the specific API semantically requires a non-empty set.
|
||||
|
||||
## Testing workflow
|
||||
|
||||
Use focused targets first:
|
||||
|
||||
- Property/schema/attribute/validation: `structive_property_core_test`.
|
||||
- Runtime type-erased API: `structive_property_runtime_api_test`.
|
||||
- Synchronization/guard/concurrency: `structive_property_synchronization_test`.
|
||||
- Presentation extension: `structive_property_extensions_test`.
|
||||
- Public headers: `structive_header_*` tests.
|
||||
- Installed consumer: `structive_install_consumer_test`.
|
||||
|
||||
Then run the standalone suite:
|
||||
|
||||
```text
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DSTRUCTIVE_BUILD_TESTS=ON -DSTRUCTIVE_INSTALL=ON
|
||||
cmake --build build
|
||||
ctest --test-dir build --output-on-failure
|
||||
```
|
||||
|
||||
For synchronization changes, run TSan when the compiler/platform supports it. For Core public API changes, run Debug and Release and then verify the Adminive consumer suite.
|
||||
|
||||
## CMake/install rules
|
||||
|
||||
- Keep `structive::property_core` header-only and `structive::property_extensions` as the extension target unless a deliberate design change says otherwise.
|
||||
- Examples/tests default based on top-level use; embedding Structive as a subdirectory must not force examples into the parent build.
|
||||
- Standalone install/export must continue to support `find_package(Structive CONFIG REQUIRED COMPONENTS Core Extensions)`.
|
||||
- Use paths relative to the Structive CMake file that owns them.
|
||||
|
||||
## Review checklist
|
||||
|
||||
Before handoff, verify:
|
||||
|
||||
- No second object model or external policy leaked into Core.
|
||||
- Intrinsic capability still matches Accessor capability.
|
||||
- Stored read-only properties still contribute zero lock slots.
|
||||
- Move-only typed write and runtime copy-write semantics remain distinct.
|
||||
- Validation is still explicit.
|
||||
- Extension metadata uses the shared Attribute protocol and Core does not interpret it.
|
||||
- New static invariants have compile-fail coverage.
|
||||
- Public headers compile alone and the standalone install consumer works.
|
||||
- Adminive still builds/tests if the public Structive contract changed.
|
||||
- Only checks actually run are reported as passing.
|
||||
@@ -0,0 +1,89 @@
|
||||
# Structive Codex Instructions
|
||||
|
||||
## Mandatory skill usage
|
||||
|
||||
- Use `$structive-development` before editing Structive Core, Extension metadata, runtime access, synchronization, tests, CMake, install/export, or public documentation.
|
||||
- Do not use Git commands or modify repository history.
|
||||
|
||||
## Project purpose
|
||||
|
||||
Structive augments ordinary C++ structs with explicit property schema, intrinsic capability, attributes, constraints, optional managed synchronization, and a runtime adapter boundary. It does not replace the native object model.
|
||||
|
||||
Read `docs/DESIGN.zh-CN.md` (or `docs/DESIGN.md`) before changing architecture, and `docs/CORE_GUIDE.zh-CN.md` for the concrete API.
|
||||
|
||||
## Non-negotiable architecture rules
|
||||
|
||||
- Enhance native C++ structs; do not introduce a second object model such as mandatory `Property<T>` storage wrappers.
|
||||
- Registration is explicit. Members not present in the Schema are ordinary C++ state.
|
||||
- Intrinsic `readable/writable` capability describes structure, not authorization or user permission.
|
||||
- Raw C++ access and managed access are intentionally different contracts.
|
||||
- Stored intrinsic read-only properties must remain outside mutable lock topology and keep the zero-lock managed-read fast path.
|
||||
- Static facts should fail or optimize at compile time through `constexpr`, concepts, `requires`, and `static_assert`.
|
||||
- Runtime access is for dynamic adapters, not the primary C++ business API.
|
||||
- Runtime write is a copy-input boundary. `Property::writable` and `Property::runtime_copy_writable` have different meanings; move-only typed writes must remain possible when the accessor supports them.
|
||||
- Validation remains explicit; do not hide validation, events, persistence, transactions, or rollback inside ordinary `write()`.
|
||||
- Synchronization protects mutable consistency only. It is not visibility, authorization, persistence, or transaction policy.
|
||||
- Core has one Attribute protocol. New domains define Extension-owned categories instead of adding domain enums/branches to Core.
|
||||
- Extension dependency direction is `Extension -> Core`; Core must never include or interpret Presentation/Adminive/other extension semantics.
|
||||
|
||||
## Coding style
|
||||
|
||||
- C++ uses K&R brace style.
|
||||
- Do not add meaningless blank lines.
|
||||
- Keep comments adjacent to the code they explain.
|
||||
- Preserve existing public function signatures and semantic meaning when modifying implementations.
|
||||
- Replaced implementations are deleted; do not add compatibility wrappers unless the design explicitly requires them.
|
||||
- Avoid repeated defensive checks in internal layers when the invariant is already established by the outer/schema boundary.
|
||||
- CMake paths are relative to the owning CMake file through `CMAKE_CURRENT_LIST_DIR`; do not build paths from a parent project's source directory.
|
||||
- Do not introduce random build/output path names.
|
||||
- If PowerShell is needed, use PowerShell 7 through `pwsh`.
|
||||
|
||||
## Core versus Extension ownership
|
||||
|
||||
Core owns:
|
||||
|
||||
```text
|
||||
Property identity/key
|
||||
Accessor/intrinsic capability
|
||||
Object_Schema / Type_Descriptor
|
||||
Attribute protocol
|
||||
Constraint / explicit validation helpers
|
||||
Synchronization plan and resolved lock topology
|
||||
Property_Object managed typed access
|
||||
Dynamic runtime structural access
|
||||
```
|
||||
|
||||
Extensions own domain interpretation such as presentation labels, descriptions, groups, order, persistence hints, RPC names, or documentation metadata.
|
||||
|
||||
Do not move Adminive concepts such as editable, Form, Collection, AMIS, HTTP, CRUD, or `Resource_Transaction` into Structive.
|
||||
|
||||
## Tests and verification
|
||||
|
||||
- Positive runtime/compile-time behavior belongs in Core/Extension tests.
|
||||
- A rule whose contract is “this must not compile” belongs in the compile-fail matrix rather than a runtime test.
|
||||
- Every public header must independently compile.
|
||||
- Changes to install/export behavior require the standalone install consumer.
|
||||
- Synchronization changes require focused concurrency tests and TSan when available.
|
||||
- Run Debug and Release tests for substantive Core changes because many guarantees are compile-time/template-sensitive.
|
||||
- Do not claim a sanitizer/compiler matrix passed unless it was actually executed.
|
||||
|
||||
Standalone commands from the Structive directory:
|
||||
|
||||
```text
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DSTRUCTIVE_BUILD_TESTS=ON -DSTRUCTIVE_INSTALL=ON
|
||||
cmake --build build
|
||||
ctest --test-dir build --output-on-failure
|
||||
```
|
||||
|
||||
When Structive is edited inside Adminive, also run the relevant Adminive tests because Adminive is an important consumer of the public Structive contract.
|
||||
|
||||
## Review priorities
|
||||
|
||||
1. Native-object-model preservation.
|
||||
2. Intrinsic capability versus external policy boundary.
|
||||
3. Compile-time/schema invariant correctness.
|
||||
4. Read-only zero-lock guarantee.
|
||||
5. Lock ordering/group/override correctness and reference lifetime.
|
||||
6. Runtime adapter result semantics, including copy-write support.
|
||||
7. Extension-to-Core dependency direction.
|
||||
8. Public-header independence, compile-fail matrix, install consumer, and Adminive consumer compatibility.
|
||||
@@ -13,8 +13,8 @@ struct structive::Type_Descriptor<Device> {
|
||||
static auto get() {
|
||||
return object<Device>(
|
||||
defaults(presentation::group<"Environment">),
|
||||
field<&Device::temperature>(key<"temperature">, presentation::label<"Temperature">, presentation::order<2>),
|
||||
field<&Device::pressure>(key<"pressure">)
|
||||
field<&Device::temperature>(key<"temperature">, presentation::label<"Temperature">, presentation::description<"Current temperature">, presentation::order<2>),
|
||||
field<&Device::pressure>(key<"pressure">, presentation::group<"Pressure">)
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -24,10 +24,16 @@ int main() {
|
||||
static_assert(std::remove_cvref_t<decltype(temperature)>::template has_attribute<presentation::Label_Category>);
|
||||
auto temperature_info = presentation::describe<&Device::temperature>(device.schema());
|
||||
auto pressure_info = presentation::describe<&Device::pressure>(device.schema());
|
||||
auto pressure_by_index = presentation::describe<1>(device.schema());
|
||||
REQUIRE(temperature_info.label == "Temperature");
|
||||
REQUIRE(temperature_info.description == "Current temperature");
|
||||
REQUIRE(temperature_info.group == "Environment");
|
||||
REQUIRE(temperature_info.order == 2);
|
||||
REQUIRE(temperature_info.has_order);
|
||||
REQUIRE(pressure_info.label == "pressure");
|
||||
REQUIRE(pressure_info.group == "Environment");
|
||||
REQUIRE(pressure_info.group == "Pressure");
|
||||
REQUIRE(!pressure_info.has_order);
|
||||
REQUIRE(pressure_by_index.key == "pressure");
|
||||
REQUIRE(pressure_by_index.group == "Pressure");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user