/hook-code-style
looks up version-2 business-hook shape
It does not write a workspace plan. It does not plan one hook. It does not treat observed legacy code as a rule.
Slash
Use this skill while you author or review a hook body. Pipeline skills set disable-model-invocation: true, except this skill.
Skill path
If the slash name does not start the skill, put the skill file on line 1.
Ground a hook file
A seen hook file is the operand for lookup. This skill does not write a plan.
Examples
Thought1s
Style-only work is usually scope=OUT. Still emit the thinking.md audit line.
Pattern grammar owns execute() order. Observed code is evidence, not authority.
Worked for 24s
Version-2 structure, naming, comments, APIs, and verification for apps/api/src/app/hooks/.
Use with /hook-code or /hook-critic.
Version-2 hook shape
Authority: runtime TypeScript types and Builder implementations, then MCP, then the selected pattern agent, then this guide.
This guide governs new and materially edited business hooks. It replaces legacy statistical rules from injected blocks and version-1 hooks.
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
A popular deviation does not become a rule.
File and class names: Naming conventions.
Closed LogicHookPattern set: .agents/rules/hook-utils-patterns.md.
Bind the file shape
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*/"
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.
| 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. |
Bind execute() order
- 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.
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.
Bind FlowQuery and calls
Ordinary operations: .agents/rules/query/api.md. Deferred bulk: .agents/rules/query/bulk.md. See Flow Query.
- 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.
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.
Bind validation and comments
- 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.
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.
- 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 the 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
Next steps
When you review a *.hook.ts file, run /hook-critic.