添加AGENTS SKILL

This commit is contained in:
2026-08-08 21:45:29 +08:00
parent c51c9d2937
commit d2cda83810
3 changed files with 277 additions and 3 deletions
@@ -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.
+89
View File
@@ -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.
+9 -3
View File
@@ -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;
}