| name | semantic-logic-modeling |
| description | Use for all coding, refactoring, reviewing, or testing tasks that involve logic expressions, branching, predicates, validation, permissions, feature flags, derived state, rules, workflows, pricing, scheduling, UI behavior, requirements-to-code translation, or other condition-to-result decisions. Guides complex logic through variable-aggregation layers: external inputs, original atomic predicates, derived atomic predicates, composite case predicates, controlled result functions, and validation before integrating with frameworks such as React. |
Semantic Logic Modeling
Use this skill whenever code contains non-trivial logic: boolean expressions, condition trees, repeated predicates, state derivation, validation, permission checks, feature gates, workflow routing, pricing rules, UI behavior, or tests for those rules.
Core idea: do not hide business logic inside raw expressions. Convert it into named, reusable semantic functions in layers, then let application code consume those functions.
When logic starts from product requirements, treat the work as variable aggregation:
external inputs -> original atomic predicates -> derived atomic predicates -> composite cases -> controlled results
This flow should form a directed acyclic dependency graph. Higher layers may depend on lower layers; lower layers should not reach upward into result or framework code.
A minimal complete model still needs three roles: external inputs, at least one atomic semantic predicate, and at least one controlled result. Add derived predicates and composite cases when they clarify reuse, traceability, or scenario coverage.
Required Workflow
-
Identify requirement cases and external inputs.
- If a natural-language requirement, ticket, or design note exists, extract condition factors and effect factors before coding.
- Condition factors are the facts that must hold, such as identity, quota, configuration, selection, time, status, or permission.
- Effect factors are the controlled outcomes, such as button text, disabled state, visible prompt, allowed action, route, price, status, or API command.
- For multiple scenarios, build a compact case table mentally or in tests: left side conditions, right side effects. Merge duplicate condition combinations.
- Raw facts from APIs, databases, user input, route params, config, time, environment, or service state.
- Keep these names close to their source, e.g.
accountLevel, remainingPower, isPreprocessed.
-
Extract original atomic semantic predicates.
- Convert each smallest meaningful judgment into a pure function.
- One function should answer one business question.
- These predicates are the first semantic wrapper around external inputs, e.g. equality checks, comparisons, nullability, membership, and one-step boolean judgments.
- Prefer functions over local variables when logic may be reused or tested.
-
Extract derived atomic semantic predicates.
- Compose atomic predicates into reusable intermediate meanings.
- Do not repeat the same expression in multiple places.
- Avoid scattering raw negation such as
!isVip(...); define a named opposite when reused, important, or part of a mutually exclusive pair.
- When two meanings are exact opposites, derive one from the other instead of reimplementing both sides independently.
-
Extract composite case predicates.
- A composite case represents a complete scenario, usually an AND combination of semantic predicates.
- Name it after the business case, not after implementation details.
- Each row in a case table should usually become one composite case predicate.
- Composite cases are the best place to detect overlapping scenarios and missing scenarios.
-
Define controlled result functions.
- Final action/output/status/UI/workflow variables should consume semantic predicates only.
- Application branches should depend on these result functions rather than raw conditions.
- Controlled results may combine multiple composite cases, usually as an OR over named cases.
-
Validate traceability, coverage, and conflicts.
- Trace every controlled result back to its composite cases, derived predicates, original atomic predicates, and external inputs.
- For each semantic variable, identify predecessor variables that trigger it and successor variables that it affects.
- For small finite domains, enumerate relevant dimensions and assert the expected composite cases/results.
- If the domain expects one active scenario, assert that no input combination activates two conflicting composite cases.
- Treat uncovered or overlapping cases as requirement ambiguity or logic defects, not as incidental test failures.
-
Integrate into framework or runtime code.
- Components, controllers, handlers, jobs, validators, and resolvers should be thin consumers of semantic logic.
- Framework-specific wrappers come after pure functions, not before them.
Construct Layers
| Layer | Purpose | Code shape |
|---|
| External inputs | Preserve raw facts from outside the logic model. | accountLevel, remainingPower, isPreprocessed |
| Original atomic predicates | Give one raw expression one business meaning. | isBasicMember(ctx), isPowerEnough(ctx) |
| Derived atomic predicates | Compose or invert atomic meanings into another reusable single meaning. | isNotProMember(ctx), isMeetSdxlCondition(ctx) |
| Composite case predicates | Represent a complete requirement scenario. | isBasicOrProMemberTrainConfigPending(ctx) |
| Controlled result functions | Produce final UI, workflow, permission, validation, or API outcomes. | getTrainButtonState(ctx), resolveApprovalRoute(ctx) |
Use the vocabulary even when the codebase uses different file names. It gives the refactor a stable mental model.
Layer Constraints
Use these constraints as a quick check when deciding which layer a variable belongs to.
| Layer | Inputs | Operation | Output |
|---|
| External inputs | None inside the model | Read raw API, database, user, route, config, time, or environment facts | Any type |
| Original atomic predicates | One or two external input fields | Equality, comparison, membership, nullability, or simple boolean judgment | Boolean |
| Derived atomic predicates | One or more atomic predicates | Logical NOT, AND, OR, or semantic aliasing into one reusable meaning | Boolean |
| Composite case predicates | Two or more atomic/derived predicates | Usually AND over every condition required by a scenario | Boolean |
| Controlled result functions | One or more atomic, derived, or composite predicates | Usually OR across cases, or a resolver that maps cases to final behavior | Boolean or structured result |
Keep external data influence isolated in original atomic predicates whenever possible. Higher layers should talk in domain meanings, not raw API fields.
Opposites And Deduction Chains
For exact opposite meanings, define a paired atomic semantic predicate and derive the negative side from the source predicate.
export function isProMember(ctx: TrainContext) {
return ctx.accountLevel === 3;
}
export function isNotProMember(ctx: TrainContext) {
return !isProMember(ctx);
}
- In snake_case models or diagrams, name the pair
is_xxx and is_not_xxx, e.g. is_vip / is_not_vip.
- In camelCase TypeScript/JavaScript code, keep the same semantics as
isXxx / isNotXxx, e.g. isProMember / isNotProMember.
- Treat an exact opposite pair as one-to-one: one source predicate and one derived opposite.
- Do not implement both sides independently from raw inputs; independent definitions can drift and create contradictions.
- Avoid raw negation such as
!isVip(ctx) outside the predicate layer when the negative meaning is reused or important to the domain.
A deduction chain is the ordered dependency path created by semantic predicates across layers. It starts at external inputs, passes through original and derived atomic predicates, may pass through composite case predicates, and ends at controlled results. Use the chain to explain why a result happened, to find contradictions, and to stop tracing when you reach original atomic predicates that map directly back to requirement facts.
Hard Rules
- Reuse is mandatory. If the same judgment is needed in more than one place, extract it into a shared pure function before use.
- Prefer atomic-grained functions at the first definition point. Build larger functions by composition.
- Keep logic pure by default: explicit inputs, deterministic outputs, no side effects, no hidden reads from global state.
- Do not duplicate raw business expressions across files, components, hooks, handlers, or tests.
- Keep raw expressions at the lowest semantic layer only. Higher layers must call named functions.
- Preserve dependency direction: external inputs feed atomic predicates, atomic predicates feed derived/composite predicates, and controlled result functions feed application code.
- Keep predecessor/successor relationships acyclic. A predicate must not depend on a result that it helps produce.
- Do not let framework state, JSX, controller branches, or result builders become the only place where a business judgment exists.
- Make exact opposites explicit when the domain cares about both sides; derive the opposite from the source predicate to avoid contradictions.
- When a set of composite cases is intended to be mutually exclusive, test exclusivity.
- When a finite input space is small enough to enumerate, test coverage by enumerating combinations.
- Keep names business-readable. A reader should understand the rule without mentally executing an expression.
- Preserve the codebase's existing module style, naming conventions, and test patterns unless they conflict with these rules.
- Add focused tests for extracted predicate and result functions when logic has business impact or multiple branches.
Layer Pattern
type TrainContext = {
accountLevel: number;
remainingPower: number;
expectedConsumePower: number;
currentSelectedModelId: number;
isPreprocessed: boolean;
};
export function isBasicMember(ctx: TrainContext) {
return ctx.accountLevel === 2;
}
export function isProMember(ctx: TrainContext) {
return ctx.accountLevel === 3;
}
export function isPowerEnough(ctx: TrainContext) {
return ctx.remainingPower >= ctx.expectedConsumePower;
}
export function isSdxlSelected(ctx: TrainContext) {
return ctx.currentSelectedModelId === 18;
}
export function isConfigPending(ctx: TrainContext) {
return !ctx.isPreprocessed;
}
export function isNotProMember(ctx: TrainContext) {
return !isProMember(ctx);
}
export function isBasicOrProMember(ctx: TrainContext) {
return isBasicMember(ctx) || isProMember(ctx);
}
export function isPowerNotEnough(ctx: TrainContext) {
return !isPowerEnough(ctx);
}
export function isNotMeetSdxlCondition(ctx: TrainContext) {
return isSdxlSelected(ctx) && isNotProMember(ctx);
}
export function isMeetSdxlCondition(ctx: TrainContext) {
return !isNotMeetSdxlCondition(ctx);
}
export function isMeetTrainCondition(ctx: TrainContext) {
return isPowerEnough(ctx) && isMeetSdxlCondition(ctx);
}
export function isBasicOrProMemberTrainConfigPending(ctx: TrainContext) {
return isBasicOrProMember(ctx) && isMeetTrainCondition(ctx) && isConfigPending(ctx);
}
export function getTrainButtonState(ctx: TrainContext) {
return {
disabled: isBasicOrProMemberTrainConfigPending(ctx),
showPowerPrompt: isPowerNotEnough(ctx),
showSdxlPrompt: isNotMeetSdxlCondition(ctx),
};
}
Requirement Extraction Pattern
When starting from a requirement, translate prose into condition/effect cases before writing code.
Case: "Basic members with enough power and no SDXL restriction, before config is complete, cannot start training."
Condition factors:
- basic member
- power enough
- SDXL condition met
- config pending
Effect factors:
- train button disabled
- train button text resolves to "Start training"
- power prompt hidden
Then map each condition factor to an atomic or derived predicate, each full condition set to a composite case, and each effect factor to a controlled result.
Validation Pattern
Prefer tests that exercise the semantic layers directly.
- Atomic predicate tests prove raw inputs are interpreted correctly.
- Derived predicate tests prove negation and composition do not drift.
- Composite case tests prove complete scenarios activate as expected.
- Result function tests prove final behavior only depends on named predicates.
- Enumeration tests check mutually exclusive dimensions such as member tier, configuration state, quota state, model selection, permission state, workflow status, or feature flag state.
For enumeration tests, assert the business invariant explicitly. Examples: exactly one button text result is active, at most one mutually exclusive case is true, all expected finite combinations have a defined result, or known ambiguous combinations are documented as product gaps.
Naming Guidance
- Boolean predicates: use
is, has, can, should, needs, show, or allows.
- Atomic predicates: name the smallest business fact, e.g.
isPowerEnough, hasActiveSubscription.
- Derived predicates: name the inferred meaning, e.g.
isMeetTrainCondition, canUseSdxlModel.
- Composite cases: name the scenario, e.g.
isBasicMemberConfigPending.
- Controlled results: name the result, e.g.
getTrainButtonState, shouldBlockCheckout, resolveApprovalRoute.
- Prefer positive semantic names. Use named negative predicates only when the negative meaning is reused or is a domain concept.
- Keep the codebase naming style. The variable-aggregation model often uses
snake_case in diagrams, but TypeScript/JavaScript codebases commonly prefer camelCase; do not mix styles just to mirror a diagram.
- Avoid overlong names. Include enough domain words to remove ambiguity, then stop.
Placement
- Put reusable logic in a domain/policy/rules/predicates module near the owning business domain.
- Keep framework wrappers in separate files when useful.
- Tests should target pure functions directly; integration tests should verify framework wiring.
Example folders:
src/domain/train/trainPredicates.ts
src/domain/train/trainResults.ts
src/domain/train/trainPredicates.test.ts
React
For React, first extract pure functions, then wrap them in shared hooks, then keep components render-focused. Read references/react.md when implementing or refactoring React components/hooks.