/hook-code-style
Version-2 business-hook structure, naming, comments, APIs, and verification. This skill is not a planner.
Outcome
You can look up the shape of a new or materially edited business hook under apps/api/src/app/hooks/.
Assumptions
- Skill file:
.agents/skills/hook-code-style/SKILL.md - Closed
LogicHookPatternset:.agents/rules/hook-utils-patterns.md - File and class names: Naming conventions
Non-goals
- Do not plan a workspace change. Use
/logic-plan. - Do not plan one hook. Use
/hook-plan. - Do not treat observed legacy code as a rule.
Contract
This guide governs new and materially edited business hooks. It replaces legacy statistical rules from injected blocks and version-1 hooks.
Style-only work is usually scope=OUT. Still emit the thinking.md audit line.
Use with hook-coder or /hook-critic. Pattern grammar owns execute() order.
Authority
When guidance conflicts, use this order:
- runtime TypeScript types and Builder implementations
- MCP-verified schemas and graphs
- the selected pattern agent
- this guide
- vetted version-2 examples
- legacy observations
Observed code is evidence, not authority. A popular deviation does not become a rule.
Also load .agents/rules/logic/thinking.md. Do not recopy the five rules.
Syntax
If the slash name does not start the skill, put @.agents/skills/hook-code-style/SKILL.md on line 1.
Use this skill while you author or review a hook body. It is not a planner.
Complete hook grammar
hook_file ::= canonical_import_block NEWLINE decorator NEWLINE class_block
decorator ::= "@LogicHook({" NEWLINE decorator_fields NEWLINE "})"
decorator_fields ::= identity_fields "," execution_fields "," pattern_fields
{ "," optional_field }
identity_fields ::= "name:" STRING "," "slug:" METHOD_SLUG ","
"path:" HOOK_PATH "," "hookCollection:" COLLECTION_SLUG
execution_fields ::= "version: 2," "concurrency:" CONCURRENCY ","
"timeout:" INTEGER "," "idempotent:" BOOLEAN
pattern_fields ::= "pattern:" LOGIC_HOOK_PATTERN "," "patternScore:" SCORE
CONCURRENCY ::= "'parallel'" | "{ mode: 'queue', maxConcurrent:" INTEGER "}"
SCORE ::= INTEGER_0_TO_100
class_block ::= "export class" CLASS_NAME "{" doc_comment execute_method "}"
execute_method ::= "public static execute()" "{" "return async ("
bob_parameter ") => {" result_variables try_catch result_return "}" "}"
bob_parameter ::= "bob: BobRequest<{ data: { data: any; naoQueryOptions: NaoQueryOptions } }>"
result_variables ::= "let ok = true," "error: any = null," "data"
try_catch ::= "try {" START_MARKER business_logic END_MARKER "}"
"catch (err) {" "error = err" "ok = false" "}"
result_return ::= "return returnEventResult(bob, { ok, error, data })"
business_logic ::= validate_phase read_phase guard_phase transform_phase
[ write_phase ] [ side_effect_phase ] success_assignment
success_assignment ::= "ok =" VALUE NEWLINE "data =" VALUE
COLLECTION_SLUG ::= KEBAB_CASE
METHOD_SLUG ::= KEBAB_CASE
HOOK_PATH ::= COLLECTION_SLUG "/" METHOD_SLUG
CLASS_NAME ::= PASCAL_COLLECTION PASCAL_METHOD
LOGIC_HOOK_PATTERN ::= (* closed set: .agents/rules/hook-utils-patterns.md *)
START_MARKER ::= "/**START_CODE_LOGIC*/"
END_MARKER ::= "/**END_CODE_LOGIC*/"
Canonical import block
Business-hook imports are generated by scripts/mcp/core/standardize-hook-imports.ts. Do not hand-maintain a smaller set. The generator erases custom imports.
import { naoFormatErrorById, naoUtils, naoLogger, SuperJoi, naoDateTime, logg, loggStringify, InterfaceUtils, NaoExcelRunner, NaoCsvRunner2 } from '@logic-bee/utils'
import { NaoQueryOptions, flowUserSession, FlowUser, flowGlobal, naoProcessManager } from '@logic-bee/flow-query'
import { AgGridSearchPlugin, AgGridTreeSearchPlugin } from '@logic-bee/data-flow'
import { BobRequest, returnEventResult } from '@logic-bee/event-engine'
import { FlowDocument, naoInterfaces } from '@logic-bee/interfaces'
import { naoBusinessUnits, policyManager } from '@logic-bee/config'
import { merge, Observable } from 'rxjs'
import { LogicHook } from '@logic-bee/logic-hooks'
Do not add a package import only for hook-body convenience. Prefer canonical platform utilities. If a required runtime dependency is absent, stop. Resolve the generator contract.
Decorator fields
| Field | Rule |
|---|---|
name | Human-readable. Not the class name. |
slug | Method directory name. |
path | Exactly <hookCollection>/<slug>. |
hookCollection | First-level directory and collection.json slug. |
version | 2 for new hooks. |
pattern | Exact LogicHookPattern from the specialist. |
patternScore | 0 unless independently audited as a template. |
idempotent | From replay behavior. Do not accept the scaffold default blindly. |
timeout / concurrency | From measured work and ordering needs. |
legacy | Only to preserve an existing registered identity. |
Execute and result
- One exported class. One public static
execute(). No additional class methods. - Local
constfunctions are allowed when they clarify repeated local behavior. - Keep
/**START_CODE_LOGIC*/and/**END_CODE_LOGIC*/exact and balanced. - Every successful branch assigns both
okanddata. - Early success returns use
returnEventResult(bob, { ok, error, data })after both assignments. - Do not assign
errorinside business logic. Throw. Let the wrapper catch it. - Business errors use
naoFormatErrorByIdwith a user-readable reason. - Side-effect-only success returns
data = {}or a meaningful statistics object. - Pattern agents define specialized result shapes.
Pipeline order
Use the selected pattern file ## Pattern grammar concatenation as the execute() order. Reordering those productions is a BLOCKER.
The generic sequence is a fallback only when that production is absent:
- Normalize and validate input.
- Load required documents and settings.
- Enforce domain, status, and authorization guards.
- Transform or calculate.
- Persist if the selected pattern permits writes.
- Invoke downstream effects.
- Verify results and assign
ok/data.
Do not write before all inexpensive preconditions are known. Do not invoke downstream side effects before required local persistence succeeds. The generic sequence cannot excuse a Pattern-grammar violation.
FlowQuery
- Ordinary operations:
.agents/rules/query/api.md - Deferred bulk:
.agents/rules/query/bulk.md - Start with
bob.flowQuery('flowSlug/docName'). Legacy collection APIs are forbidden. - Verify document paths, fields, interfaces, statuses, and data type values with MCP.
- Use an interface key on supported read terminals.
getOne()may returnnull. Guard it. UsegetFirst()only for throwing semantics.- User-authorized operations use
addPolicy(). - Any scope, session, validation, or immutability bypass needs a comment that explains why.
- Prefer typed filters and narrow projections. Bound all list queries.
all()requires proof that the result is safely bounded by the domain.
See Flow Query.
Hook and action calls
hook_call ::= "bob.executeHook(" HOOK_PATH ")"
".requestPayload(" PAYLOAD ")" { call_modifier } call_terminal
action_call ::= "bob.executeAction(" ACTION_PATH ")"
".requestPayload(" PAYLOAD ")" { call_modifier } call_terminal
call_modifier ::= ".documentCfpPath(" DOCUMENT_PATH ")"
| ".responseDocumentInterface(" INTERFACE_KEY ")"
| ".responseInterface(" INTERFACE_KEY ")"
| ".skipEvent()" | ".skipSession()"
call_terminal ::= ".execute()" | ".executeAsTask()"
- Resolve targets through
hook_graph. - Use
executeHookfor decorator paths. UseexecuteActionfor CFP action paths. - Inspect failure results. Use
.throwIfErrors()where the returned builder response supports it. - Fire-and-forget is forbidden when later logic depends on the outcome.
- Legacy
runAction,runActionOrThrow,executeRequest, andexecuteRequestOrThroware forbidden in new code.
Validation, precision, naming
- Validate required IDs and payload shape before use.
- Required fetches get immediate missing-document guards.
- Status transitions must be verified against document definitions.
- Error text names the failed business object and the correction. Do not leak secrets.
- Blocking violations throw. Nonfatal calculate or validator messages use the platform
formMessagescontract when the caller expects it. - Do not catch an error only to continue unless the pattern owns partial failure and records it in the result.
- Financial and quantity arithmetic uses
naoUtils.mathChain()with explicit precision. - Date construction, parsing, comparison, and arithmetic uses
naoDateTime(). - Use
naoUtils._instead of importing lodash. - Use
naoInterfaces.setType<>for data shapes andsetDocumentType<>for full documents. - Use
anyonly at a verified dynamic boundary. Narrow it as soon as possible. - Variables and local functions use descriptive camelCase. Singular names mean one document. Plural names mean collections.
- Boolean names start with
is,has,can,should, orwas. - Write results use
insertResult,updateResult,deleteResult, or a local$suffix. Do not mix conventions in one hook.
Comments
The class member JSDoc is an orchestration contract. Include only applicable tags:
- business purpose and lifecycle stage
@domainand governing invariant@callsfor downstream hooks, actions, or documents@artifactFlowfor important input and output documents@rule,@guard,@transaction,@throws
Inline comments use // -->Verb: business intent. They explain why a step exists. They do not explain the syntax below.
Replay, sessions, and control flow
- Explain what happens when the same input runs twice.
idempotent: truerequires a stable lookup or deduplication key, or a pure no-write proof.- Automatic FlowQuery sessions are the default for local writes.
skipSession()is a boundary decision. It is not a performance decoration.- External calls cannot be claimed as part of a MongoDB transaction.
- Cross-domain operations require both sides of the contract and a rollback, compensation, or durable recovery policy.
- Construct persisted objects with explicit fields for financial, status, identity, and audit-sensitive data.
- Prefer guard clauses over deep nesting.
- Use
for...ofwhen awaiting sequential work. - Use bounded
Promise.allonly when independence and concurrency safety are proven. - Use
Mapfor repeated keyed lookups.
Forbidden in new hooks
- version 1
- missing or generic pattern without classification evidence
- guessed document, interface, or value names
- direct MongoDB or driver access
- legacy FlowCollection APIs
- raw
throw new Errorfor business failures - unbounded reads or concurrency
- hidden side effects in calculate, validator, estimate, report, or utility patterns
- manual edits to generated barrels or registries
- additional exported helpers or class methods
- secrets or sensitive payloads in logs
Review checklist
hard_rules:
- H1: one decorated exported class and one public static execute method
- H2: version is 2 and pattern is one exact LogicHookPattern value
- H3: identity metadata matches directory, filename, and class
- H4: code markers, catch block, and returnEventResult wrapper are exact
- H5: every success path assigns ok and data together
- H6: errors are structured and required fetches are guarded
- H7: all document paths, fields, interfaces, statuses, and datatypes are verified
- H8: only modern FlowQuery and hook/action APIs are used
- H9: selected pattern's read/write/return constraints hold
- H10: idempotency, session, transaction, and concurrency choices are justified
- H11: domain and cross-domain invariants hold
- H12: typecheck and hook diagnostics introduce no failures
soft_rules:
- S1: execute body matches the selected Pattern grammar order; the generic pipeline applies only when that grammar has no stricter sequence
- S2: names are semantic and comments explain business intent
- S3: queries are narrow, typed, projected, and bounded
- S4: major data structures use naoInterfaces types
- S5: calculations and dates use platform wrappers
Related
/hook-plan/hook-critic- Naming conventions
- Bob wrapper
- Query rules:
.agents/rules/query/api.md,.agents/rules/query/bulk.md - Pattern catalog:
.agents/rules/hook-utils-patterns.md - Implementer:
.agents/rules/hook/coder.md