| name | crane-natspec |
| description | This skill should be used when the user asks about "natspec", "documentation", "include-tag", "selector", "cast", "@custom:signature", "@custom:selector", "@custom:topiczero", "@custom:interfaceid", "AsciiDoc", or needs guidance on documenting Crane contracts with NatSpec and AsciiDoc include-tags. |
| license | MIT |
Crane NatSpec & Documentation Standards
Crane uses NatSpec combined with AsciiDoc include-tags for accurate, extractable documentation.
Critical Alignment Requirements (LR-3):
- Skills reflect finalized standards per PRD: NatSpec verification exclusively uses the dedicated Foundry Script (
scripts/foundry/ComputeNatSpecValues.s.sol) + central values file (NEVER ad-hoc cast or manual for authoritative @custom values).
- Use
CENTRALLY_COMPUTED_NATSPEC_VALUES.md exclusively (ONLY those values) for all @custom:selector, @custom:topiczero, @custom:interfaceid in examples/docs.
- Full scope (LR-1): applies to all Solidity code including production + every test file (
.t.sol, TestBase_*.sol, Behavior_*.sol, handlers, stubs, comparators, etc.).
- ERC1967: when documenting storage Repos, use/show the compliant
DEFAULT_SLOT = bytes32(uint256(keccak256(abi.encode("hier.name"))) - 1) form (LR-6); cross-ref crane-architecture.
- Ties to GitBook/LR-2: accurate centrally-verified NatSpec enables required content on Create3FactoryDFPkg chain setup, reusable
DiamondPackageCallBackFactory (interfaceId 0x949da331 from central), registries, ported protocol TestBases+Behaviors+utilities, and general utilities (Sets etc.).
- Test NatSpec (LR-7): public surfaces on test infrastructure must carry exact // tag:: + @custom using central values + Behavior declaration tests validate the documented surfaces.
- References:
docs/development/natspec.md, PRD LR-1/LR-3/LR-7, AGENTS.md (NatSpec section), crane-testing/crane-deployment skills. Update this skill on standard evolution.
AsciiDoc Include-Tags
Wrap documented symbols with include-tags for documentation extraction:
// tag::MySymbol[]
/// @notice Description of the symbol
function myFunction() external { ... }
// end::MySymbol[]
Tag Format Rules
- Tag markers must match exactly (no extra spaces inside
[])
- Tag name should match the symbol being documented
- Use PascalCase for tag names matching type names
- Use camelCase for function tag names
Example Usage
// tag::transfer[]
/// @notice Transfers tokens to a recipient
/// @param to_ The recipient address
/// @param amount_ The amount to transfer
/// @return success True if transfer succeeded
/// @custom:signature transfer(address,uint256)
/// @custom:selector 0xa9059cbb
function transfer(address to_, uint256 amount_) external returns (bool success);
// end::transfer[]
Custom NatSpec Tags
Functions
| Tag | Purpose | Example |
|---|
@custom:signature | Canonical signature string | transfer(address,uint256) |
@custom:selector | bytes4 selector | 0xa9059cbb |
/// @notice Transfers tokens
/// @custom:signature transfer(address,uint256)
/// @custom:selector 0xa9059cbb
function transfer(address to_, uint256 amount_) external returns (bool);
Errors
| Tag | Purpose | Example |
|---|
@custom:signature | Canonical error signature | NotOwner(address) |
@custom:selector | bytes4 selector | 0x30cd7471 |
/// @notice Thrown when caller is not the owner
/// @custom:signature NotOwner(address)
/// @custom:selector 0x30cd7471
error NotOwner(address caller);
Events
| Tag | Purpose | Example |
|---|
@custom:signature | Canonical event signature | Transfer(address,address,uint256) |
@custom:topiczero | bytes32 topic0 hash | 0xddf252ad... |
/// @notice Emitted on token transfer
/// @custom:signature Transfer(address,address,uint256)
/// @custom:topiczero 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
event Transfer(address indexed from, address indexed to, uint256 amount);
Note: Events use @custom:topiczero (bytes32), not @custom:selector (bytes4).
ERC-165 Interfaces
| Tag | Purpose | Example |
|---|
@custom:interfaceid | bytes4 interface ID | 0x01ffc9a7 |
/// @title IERC165
/// @custom:interfaceid 0x01ffc9a7
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
Full Scope (incl. Tests) and Verification Script
Per LR-1:
- Scope: All Solidity code: interfaces, contracts, libraries (Repos, Targets, Services, Facets, DFPkgs, AwareRepos), and all test files (TestBase_, Behavior_, IHandler, handlers, stubs, comparators, *.t.sol etc.).
- Test code that exposes public APIs must receive the same NatSpec + exact // tag::[] + @custom:* (using ONLY central values) as production code (see LR-7 point 10).
- Declaration tests (Behavior_IFacet etc.) must validate the documented values (e.g.
facetInterfaces() returns selectors matching the @custom ones tagged in the interface/impl).
The verification script (scripts/foundry/ComputeNatSpecValues.s.sol) is the committed tool for this (see run instructions above). It itself uses // tag::ComputeNatSpecValues[] etc. and documents the process.
NatSpec on Test Code (LR-1 + LR-7)
Example for a Behavior or TestBase public surface (use central IFacet values):
// tag::Behavior_IFacet[]
/// @title Behavior_IFacet
/// @notice Validation helpers for IFacet (see central for selectors e.g. facetInterfaces 0x2ea80826)
library Behavior_IFacet {
// tag::areValid_IFacet_facetInterfaces[]
/// @notice ...
/// @custom:signature areValid_IFacet_facetInterfaces(IFacet,bytes4[],bytes4[])
/// @custom:selector 0x... (use central or script)
function areValid_IFacet_facetInterfaces(...) public view returns (bool) { ... }
// end::areValid_IFacet_facetInterfaces[]
}
// end::Behavior_IFacet[]
Verification Script and Central Values Process (Mandatory per LR-1)
Primary method: Use the dedicated Foundry Script (committed in repo) for all authoritative values. Run:
forge script scripts/foundry/ComputeNatSpecValues.s.sol --sig "run()" -vvv
- This script uses compiler (
type(IInterface).interfaceId for IDs; Solidity keccak256 for selectors/topic0) -- reproducible, CI-auditable.
- Output the exact bytes; copy to
CENTRALLY_COMPUTED_NATSPEC_VALUES.md (single source of truth) and/or into @custom:* tags.
- Helper
scripts/compute_natspec_values.sh exists for quick exploration (uses cast internally) but always verify final values via the Foundry Script.
- Re-run after any interface/symbol addition or change.
Central Values Rule (use ONLY):
- Always pull
@custom:selector / @custom:topiczero / @custom:interfaceid exclusively from docs/reports/gap/CENTRALLY_COMPUTED_NATSPEC_VALUES.md.
- Current central pass date: 2026-07-02 (example values below; always consult the file).
- Subagents must follow strict read order before work: gap report + CENTRALLY... + PRD (LR sections) + AGENTS.md + source files.
Examples of central values (use ONLY from the file, do not recompute):
-
IFacet interface surface (selectors):
facetName() : 0x5b6f4d01
facetInterfaces() : 0x2ea80826
facetFuncs() : 0x574a4cff
facetMetadata() : 0xf10d7a75
supportsInterface(bytes4) : 0x01ffc9a7
-
IDiamondPackageCallBackFactory (interfaceId 0x949da331 from central):
deploy(address,bytes) : 0xe97fac05
initAccount(address,bytes) : 0x8e85783e
- ...
-
IOperable (interfaceId 0xa7f11160):
isOperator(address) : 0x6d70f7ae
NotOperator(address) selector: 0x76c6c93a
NewGlobalOperatorStatus(address,bool) topic0: 0x26ba28058a3c072a70c8fd315037fe9b3957237cef5c61a9652a8da41c673daa
- ...
See docs/development/natspec.md "Verification Script Requirement" and "Central Values Process" + the script itself (which carries NatSpec + tags using the process).
Computing Values (for exploration only)
cast (or manual) may be used for quick local checks, but final authoritative values for code/docs must come from the Foundry Script + CENTRALLY_COMPUTED... (cast matches keccak for selectors/topics but the script is the required verifier).
Function Selector (bytes4)
cast sig "transfer(address,uint256)"
Error Selector (bytes4)
cast sig "NotOwner(address)"
Event Topic0 (bytes32)
cast keccak "Transfer(address,address,uint256)"
Interface ID (bytes4)
For interface IDs, prefer the script or:
bytes4 interfaceId = type(IMyInterface).interfaceId;
Or compute manually by XOR-ing all function selectors (cross-check with script output).
Complete Documentation Example
Use exact values from CENTRALLY_COMPUTED_NATSPEC_VALUES.md (ONLY). Gold standard tag formats use hyphenated params for overload disambiguation (see AGENTS.md + ERC8023 examples).
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
// tag::IOperable[]
/// @title IOperable
/// @notice Interface for operator-based access control
/// @custom:interfaceid 0xa7f11160
interface IOperable {
// tag::NewGlobalOperatorStatus(address-bool)[]
/// @notice Emitted when global operator status changes
/// @param operator The operator address
/// @param status The new status
/// @custom:signature NewGlobalOperatorStatus(address,bool)
/// @custom:topiczero 0x26ba28058a3c072a70c8fd315037fe9b3957237cef5c61a9652a8da41c673daa
event NewGlobalOperatorStatus(address indexed operator, bool indexed status);
// end::NewGlobalOperatorStatus(address-bool)[]
// tag::NotOperator(address)[]
/// @notice Thrown when caller is not an operator
/// @param caller The unauthorized caller
/// @custom:signature NotOperator(address)
/// @custom:selector 0x76c6c93a
error NotOperator(address caller);
// end::NotOperator(address)[]
// tag::isOperator(address)[]
/// @notice Checks if an address is a global operator
/// @param query_ The address to check
/// @return isOperator_ True if operator
/// @custom:signature isOperator(address)
/// @custom:selector 0x6d70f7ae
function isOperator(address query_) external view returns (bool isOperator_);
// end::isOperator(address)[]
// tag::setOperator(address-bool)[]
/// @notice Sets operator status
/// @param operator_ The address
/// @param status_ The status
/// @custom:signature setOperator(address,bool)
/// @custom:selector 0x558a7297
function setOperator(address operator_, bool status_) external;
// end::setOperator(address-bool)[]
}
// end::IOperable[]
For IFacet (central values: facetName 0x5b6f4d01, facetInterfaces 0x2ea80826 etc):
// tag::IFacet[]
/// @title IFacet
/// @custom:interfaceid (computed via XOR in script; see central)
interface IFacet {
// tag::facetInterfaces()[]
/// @notice ...
/// @custom:signature facetInterfaces()
/// @custom:selector 0x2ea80826
function facetInterfaces() external view returns (bytes4[] memory);
// end::facetInterfaces()[]
}
// end::IFacet[]
Validation Checklist
For each documented symbol, verify:
ERC1967 in NatSpec Context (LR-6 tie-in for LR-3)
When documenting storage libraries, the NatSpec + tags must reflect the canonical ERC1967-derived slot:
// tag::DEFAULT_SLOT[]
bytes32 internal constant DEFAULT_SLOT = bytes32(uint256(keccak256(abi.encode("crane.example.slot"))) - 1);
// end::DEFAULT_SLOT[]
See crane-architecture skill and LR-6. Old direct-keccak forms without - 1 are non-compliant.
Additional Resources
Reference Files
references/natspec-examples.md - More complete examples
docs/development/natspec.md (full LR-1 details, verification script, central process, gold standard ERC8023 files)
scripts/foundry/ComputeNatSpecValues.s.sol (the dedicated LR-1 Foundry Script)
docs/reports/gap/CENTRALLY_COMPUTED_NATSPEC_VALUES.md (use ONLY these values)
Quick Reference
| Symbol Type | Selector Tag | Hash Type |
|---|
| Function | @custom:selector | bytes4 |
| Error | @custom:selector | bytes4 |
| Event | @custom:topiczero | bytes32 |
| Interface | @custom:interfaceid | bytes4 (XOR of selectors) |
LR Ties (for Agents)
- LR-1 primary: this process + Foundry Script + full scope (tests included) + central.
- LR-2: verified NatSpec supports GitBook extraction for chain setup (Create3FactoryDFPkg), DiamondPackageCallBackFactory reuse (0x949da331), registries, protocol utilities + TestBases, Sets/math.
- LR-3: keep this skill + others in sync; global + repo
.claude/skills/.
- LR-4: documented, centrally-verified, reusable facets/packages enable "deploy once, attach everywhere" (security via verified reuse for agents; cost via no re-deploy).
- LR-7: declaration tests + NatSpec on test code use these values + Behaviors.
Update this skill (and its gap report) whenever standards evolve (LR-3). Always follow the strict read order for any NatSpec work.