From d2cda838108d68ac64779e1f7ae412aeb4b0637a Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Sat, 8 Aug 2026 21:45:29 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0AGENTS=20SKILL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agents/skills/structive-development/SKILL.md | 179 ++++++++++++++++++ AGENTS.md | 89 +++++++++ extensions/tests/presentation_test.cpp | 12 +- 3 files changed, 277 insertions(+), 3 deletions(-) create mode 100644 .agents/skills/structive-development/SKILL.md create mode 100644 AGENTS.md diff --git a/.agents/skills/structive-development/SKILL.md b/.agents/skills/structive-development/SKILL.md new file mode 100644 index 0000000..0e6b33e --- /dev/null +++ b/.agents/skills/structive-development/SKILL.md @@ -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` only for managed APIs: + +```cpp +struct Device : structive::Property_Object { + double temperature{}; + int serial_number{}; +}; +template <> +struct structive::Type_Descriptor { + static auto get() { + using namespace structive; + return object( + 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. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2e2b6ff --- /dev/null +++ b/AGENTS.md @@ -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` 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. diff --git a/extensions/tests/presentation_test.cpp b/extensions/tests/presentation_test.cpp index dfbeb80..e997fa3 100644 --- a/extensions/tests/presentation_test.cpp +++ b/extensions/tests/presentation_test.cpp @@ -13,8 +13,8 @@ struct structive::Type_Descriptor { static auto get() { return object( 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::template has_attribute); 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; }