添加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() {
|
static auto get() {
|
||||||
return object<Device>(
|
return object<Device>(
|
||||||
defaults(presentation::group<"Environment">),
|
defaults(presentation::group<"Environment">),
|
||||||
field<&Device::temperature>(key<"temperature">, presentation::label<"Temperature">, presentation::order<2>),
|
field<&Device::temperature>(key<"temperature">, presentation::label<"Temperature">, presentation::description<"Current temperature">, presentation::order<2>),
|
||||||
field<&Device::pressure>(key<"pressure">)
|
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>);
|
static_assert(std::remove_cvref_t<decltype(temperature)>::template has_attribute<presentation::Label_Category>);
|
||||||
auto temperature_info = presentation::describe<&Device::temperature>(device.schema());
|
auto temperature_info = presentation::describe<&Device::temperature>(device.schema());
|
||||||
auto pressure_info = presentation::describe<&Device::pressure>(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.label == "Temperature");
|
||||||
|
REQUIRE(temperature_info.description == "Current temperature");
|
||||||
|
REQUIRE(temperature_info.group == "Environment");
|
||||||
REQUIRE(temperature_info.order == 2);
|
REQUIRE(temperature_info.order == 2);
|
||||||
REQUIRE(temperature_info.has_order);
|
REQUIRE(temperature_info.has_order);
|
||||||
REQUIRE(pressure_info.label == "pressure");
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user