Claude-skill-registry hoon-development
Write and compile Hoon code following Nock conventions and NockApp patterns. Use when working with Hoon kernel code, modifying state structures, adding pokes/peeks, or debugging Hoon compilation.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/hoon-development" ~/.claude/skills/majiayu000-claude-skill-registry-hoon-development && rm -rf "$T"
skills/data/hoon-development/SKILL.mdHoon Development Guide
This skill helps you write idiomatic Hoon code for the Prover NockApp kernel.
Hoon Basics
File Structure
:: Comments start with :: :: Documentation comments :: |% :: Core definition (library of types and functions) +$ type-name :: Type definition structure -- |_ =state :: Door (object with state) ++ arm-name :: Arm (method/function) code --
Type Definitions
:: Atoms (basic types) @ud :: Unsigned decimal (0, 1, 42) @t :: Text/cord ('hello') @tas :: Term/symbol (%groth16, %pending) @da :: Timestamp/date (~2024.1.1) :: Complex types (list @t) :: List of text (map @ud @t) :: Map from numbers to text (unit @t) :: Optional text (~ or [~ 'value']) ?(%a %b %c) :: Union of terms :: Structures +$ person $: name=@t age=@ud email=@t == :: Unions (discriminated) +$ action $% [%create name=@t] [%update id=@ud name=@t] [%delete id=@ud] ==
State Management
State Structure
Your NockApp state should be versioned:
+$ state $: %v1 :: Version marker data=(map @ud entry) :: Your data next-id=@ud :: ID counter config=settings :: Configuration ==
Accessing State Fields
:: Read field =/ current-id next-id.state :: Read nested field =/ first-entry (~(get by data.state) 1)
Updating State Immutably
IMPORTANT: Never mutate state directly. Always create a new state.
:: Update single field state(next-id +(next-id.state)) :: Update multiple fields state(data new-data, next-id +(next-id.state)) :: Update with complex computation =/ new-data (~(put by data.state) key value) =/ new-id +(next-id.state) state(data new-data, next-id new-id)
The ++poke Pattern
All input commands flow through
++poke:
++ poke |= =cause :: Take a cause as input ^- [effects=(list effect) _state] :: Return effects and new state ?- -.cause :: Pattern match on cause tag %command-one :: Handle command one :_ state :: Return state first (reversed) :~ [%http-response 200 body] :: Effect list == %command-two :: Handle command two =/ updated-state state(...) :_ updated-state :~ [%http-response 201 body] [%log 'Action completed'] == ==
Key Patterns
-
Extract inputs from cause:
=/ input-field field.cause -
Validate inputs:
?: =(input-field 0) :_ state [%http-response 400 '{"error":"Field cannot be zero"}']~ -
Compute new values:
=/ new-id +(next-id.state) =/ entry [id data timestamp] -
Update state:
=/ updated-state state(data (~(put by data.state) key entry)) -
Return effects and state:
:_ updated-state :~ [%http-response 200 body] [%log 'Success'] ==
Map Operations
Common Map Functions
:: Insert or update =/ new-map (~(put by old-map) key value) :: Lookup (returns unit) =/ maybe-value (~(get by map) key) ?~ maybe-value :: Handle not found :: Handle found, use u.maybe-value :: Delete =/ new-map (~(del by old-map) key) :: Check existence =/ exists (~(has by map) key) :: Convert to list =/ pairs ~(tap by map) :: List of [key value] :: Get size =/ count ~(wyt by map)
Safe Map Access Pattern
=/ maybe-entry (~(get by snarks.state) id.cause) ?~ maybe-entry :: Not found - return 404 :_ state :~ [%http-response 404 '{"error":"Not found"}'] == :: Found - use u.maybe-entry =/ entry u.maybe-entry :: ... continue with entry
List Operations
:: Create list =/ my-list ~['a' 'b' 'c'] =/ empty-list ~ :: Add to front =/ new-list [item my-list] :: Iterate/map =/ doubled (turn my-list |=(x=@ud (mul x 2))) :: Filter =/ evens (skim my-list |=(x=@ud =(0 (mod x 2)))) :: Fold/reduce =/ sum (roll my-list add) :: Check if empty ?~ my-list :: Empty case :: Non-empty case, i.my-list is head, t.my-list is tail
Control Flow
Conditionals
:: If-then-else (for values) ?: condition value-if-true value-if-false :: If-null check ?~ maybe-value handle-null handle-value :: u.maybe-value is the unwrapped value :: Pattern matching ?- value %option-a result-a %option-b result-b %option-c result-c ==
Variable Binding
:: Bind variable =/ name value :: Bind with type =/ name ^- @ud 42 :: Sequential bindings =/ a 1 =/ b 2 =/ c (add a b) c :: Returns 3
Text and String Operations
Creating Text
:: Literal cord (compile-time) 'hello' :: Convert with crip (runtime) (crip "hello") :: Convert tape to cord =/ t "hello" =/ c (crip t)
Text Formatting
:: Format number to text =/ num-text (scow %ud 42) :: "42" =/ id-text (scow %ud id.cause) :: Format ID :: Convert text to tape for manipulation =/ tape-form (trip 'hello') :: "hello" :: Concatenate (as tapes) =/ result (weld "hello" " world") =/ cord-result (crip result) :: 'hello world'
JSON-ish String Building (Simplified)
:: Simple response '{"success":true,"id":42}' :: With interpolation (manual) =/ id-str (scow %ud id) (crip (weld "{\"id\":" (weld id-str "}")))
Note: The prover uses helper functions for JSON formatting. See
++format-submit-response in prover.hoon.
Common Helper Functions
From the Prover Codebase
++ format-submit-response |= id=@ud ^- tape (weld "{\"success\":true,\"id\":" (weld (trip (scow %ud id)) "}")) ++ format-snark-detail |= [id=@ud entry=snark-entry] ^- tape :: Build JSON response string ...
Creating Your Own Helpers
++ build-error-response |= message=@t ^- @t (crip (weld "{\"error\":\"" (weld (trip message) "\"}"))) ++ format-list-response |= items=(list @t) ^- @t :: Convert list to JSON array ...
Type Hints and Casts
:: Specify return type =/ value ^- @ud 42 :: Cast to type =/ entry ^- snark-entry [id proof inputs vk system submitter now %pending ~ notes] :: Useful for complex structures =/ new-state ^- state state(data new-data, next-id new-id)
The Bowl (Context)
In NockApp pokes, you have access to a
bowl with context:
++ poke |= [=cause =bowl:cask] :: Bowl is available ^- [(list effect:cask) _state] :: Access bowl fields: now.bowl :: Current timestamp (@da) our.bowl :: Our identity :: etc.
Common use: Get current timestamp
=/ timestamp now.bowl =/ entry [id data timestamp ...]
Note: In the prover, we use
now directly (it's implicitly from bowl).
Error Handling
Validation Pattern
:: Check condition ?: (lth id.cause 1) :: Return error :_ state :~ [%http-response 400 '{"error":"ID must be positive"}'] == :: Continue with valid input ...
Not-Found Pattern
=/ maybe-entry (~(get by data.state) id.cause) ?~ maybe-entry :_ state :~ [%http-response 404 '{"error":"Not found"}'] [%log (crip "Entry {(scow %ud id.cause)} not found")] == :: Entry exists, use u.maybe-entry ...
Effects
Common effect types:
[%http-response code=@ud body=@t] :: HTTP response [%log message=@t] :: Log message [%error message=@t] :: Error log
Multiple Effects
:_ state :~ [%http-response 201 body] [%log 'SNARK submitted'] [%log (crip "ID: {(scow %ud new-id)}")] ==
Compiling and Testing
Compilation
# Compile Hoon to Nock hoonc prover/hoon/prover.hoon -o prover/out.jam # Check for syntax errors hoonc --check prover/hoon/prover.hoon
Common Compilation Errors
-
Mint failure: Type mismatch
- Check your type annotations (
)^- type value - Ensure structure matches type definition
- Check your type annotations (
-
Find failure: Unknown name
- Check spelling of variables and arms
- Ensure variables are in scope (
binding)=/
-
Nest failure: Type doesn't fit expected shape
- Check structure field order
- Verify all required fields are present
Debugging Tips
-
Add log effects:
[%log 'Reached this point'] [%log (crip "Value: {(scow %ud value)}")] -
Simplify complex expressions:
:: Instead of nested calls =/ step1 (operation1 input) =/ step2 (operation2 step1) =/ step3 (operation3 step2) step3 -
Check types with hints:
=/ value ^- expected-type expression
Best Practices
- Always version your state:
$: %v1 ... - Use type hints for complex values:
^- type value - Validate inputs early: Check before updating state
- Keep arms focused: One responsibility per arm
- Use helper functions: Extract formatting and validation
- Log important operations: Help with debugging
- Handle errors explicitly: Don't assume success
- Update state immutably: Use
state(field new-value) - Test incrementally: Compile after each change
- Follow existing patterns: Match the style in prover.hoon
Quick Reference
Variable Binding
=/ name value :: Bind variable =/ name ^- @ud 42 :: With type hint
State Update
state(field value) :: Single field state(field1 val1, field2 val2) :: Multiple fields
Map Operations
(~(put by map) key value) :: Insert/update (~(get by map) key) :: Lookup (returns unit) (~(del by map) key) :: Delete ~(tap by map) :: Convert to list
Conditionals
?: condition true-branch false-branch :: If-then-else ?~ maybe-val null-branch value-branch :: If-null ?- value %a a %b b == :: Pattern match
Effects
:_ state :~ [%http-response 200 '{"ok":true}'] [%log 'Done'] ==
Example: Adding a New Poke
Let's add a
%count-snarks command:
:: 1. Add to cause type +$ cause $% ... [%count-snarks ~] == :: 2. Implement in ++poke ++ poke |= [=cause =bowl:cask] ^- [(list effect:cask) _state] ?- -.cause ... %count-snarks =/ count ~(wyt by snarks.state) =/ response (crip (weld "{\"count\":" (weld (trip (scow %ud count)) "}"))) :_ state :~ [%http-response 200 response] [%log (crip "Total SNARKs: {(scow %ud count)}")] == ==
Done! Compile and test:
hoonc prover/hoon/prover.hoon -o prover/out.jam
Additional Resources
- Hoon School: https://developers.urbit.org/guides/core/hoon-school
- Hoon Standard Library: https://developers.urbit.org/reference/hoon
- NockApp Documentation: Check the nockup project docs
Common Patterns from Prover Codebase
ID Generation
=/ new-id next-id.state :: ... use new-id ... state(next-id +(next-id.state))
Building Entries
=/ entry ^- snark-entry :* id proof inputs vk system submitter now %pending ~ notes ==
Map Insert Pattern
=/ updated-state state(snarks (~(put by snarks.state) new-id entry), next-id +(next-id.state))
Response with State
:_ updated-state :~ [%http-response 201 (crip (format-submit-response new-id))] [%log (crip "SNARK #{(scow %ud new-id)} submitted")] ==