/** * Rule-based policy system for classifying API changes. * * This module provides a declarative, fluent API for building policies * that classify changes based on multi-dimensional descriptors. * * @example * ```ts * const myPolicy = createPolicy('my-policy', 'major') * .addRule(rule('removal').action('removed').returns('major')) * .addRule(rule('addition').action('added').returns('minor')) * .addRule(rule('type-widening').aspect('type').impact('widening').returns('minor')) * .build(); * ``` */ import type { ReleaseType } from '../types'; import type { ApiChange, ChangeTarget, ChangeAction, ChangeAspect, ChangeImpact, ChangeTag, ClassifiedChange, NodeKind } from './types'; /** * A predicate that matches changes based on their descriptor. * * @alpha */ export type ChangeMatcher = (change: ApiChange) => boolean; /** * A single rule in a policy that matches changes and assigns release types. * * @alpha */ export interface PolicyRule { /** Human-readable name for the rule */ name: string; /** Predicate that determines if this rule matches a change */ matches: ChangeMatcher; /** The release type to assign when this rule matches */ releaseType: ReleaseType; /** Optional explanation of why this rule exists */ rationale?: string; } /** * A complete policy consisting of ordered rules. * * @alpha */ export interface Policy { /** Human-readable name for the policy */ name: string; /** Ordered list of rules (first match wins) */ rules: PolicyRule[]; /** Release type when no rule matches */ defaultReleaseType: ReleaseType; } /** * Fluent builder for creating policy rules. * * Rules match changes based on descriptor dimensions. All conditions * are combined with AND logic - a change must match all specified * conditions for the rule to apply. * * @alpha */ export declare class RuleBuilder { private readonly ruleName; private targetConditions; private actionConditions; private aspectConditions; private impactConditions; private tagConditions; private anyTagConditions; private notTagConditions; private nodeKindConditions; private nestedCondition?; private customMatchers; private ruleRationale?; constructor(name: string); /** * Matches changes targeting specific constructs. * Multiple targets are OR'd together. */ target(...targets: ChangeTarget[]): this; /** * Matches changes with specific actions. * Multiple actions are OR'd together. */ action(...actions: ChangeAction[]): this; /** * Matches changes affecting specific aspects. * Multiple aspects are OR'd together. */ aspect(...aspects: ChangeAspect[]): this; /** * Matches changes with specific semantic impacts. * Multiple impacts are OR'd together. */ impact(...impacts: ChangeImpact[]): this; /** * Matches changes affecting specific AST node kinds. * Multiple kinds are OR'd together. * * Use this to distinguish between different types of constructs * with the same target, e.g., function vs interface removals. * * @example * ```ts * // Only match function removals (not interface or class) * rule('function-removed').action('removed').nodeKind('function').returns('major') * * // Match interface or type-alias changes * rule('type-def-changed').nodeKind('interface', 'type-alias').returns('minor') * ``` */ nodeKind(...kinds: NodeKind[]): this; /** * Matches changes that have ALL specified tags (AND logic). * * Note: Unlike target(), action(), aspect(), and impact() which use OR logic * for multiple values, hasTag() uses AND logic - the change must have * every specified tag to match. * * @example * ```ts * // Must have BOTH 'was-required' AND 'now-optional' * rule('required-to-optional').hasTag('was-required', 'now-optional') * ``` */ hasTag(...tags: ChangeTag[]): this; /** * Matches changes that have ANY of the specified tags (OR logic). * * Use this when you want to match changes with at least one of several tags. * * @example * ```ts * // Must have 'now-optional' OR 'has-default' * rule('optional-or-default').hasAnyTag('now-optional', 'has-default') * ``` */ hasAnyTag(...tags: ChangeTag[]): this; /** * Matches changes that don't have any of the specified tags. */ notTag(...tags: ChangeTag[]): this; /** * Matches only nested changes (changes within other constructs). */ nested(isNested?: boolean): this; /** * Adds a custom matcher function. * Multiple custom matchers are AND'd together. */ when(matcher: ChangeMatcher): this; /** * Adds a rationale explaining why this rule exists. */ rationale(text: string): this; /** * Completes the rule with the specified release type. */ returns(releaseType: ReleaseType): PolicyRule; /** * Builds the composite matcher from all conditions. */ private buildMatcher; } /** * Creates a new rule builder with the given name. * * @example * ```ts * rule('export-removal') * .target('export') * .action('removed') * .returns('major') * ``` * * @alpha */ export declare function rule(name: string): RuleBuilder; /** * Builder for creating complete policies. * * @alpha */ export declare class PolicyBuilder { private readonly policyName; private readonly defaultRelease; private readonly policyRules; constructor(name: string, defaultReleaseType: ReleaseType); /** * Adds a rule to the policy. * Rules are evaluated in order; first match wins. */ addRule(policyRule: PolicyRule): this; /** * Adds multiple rules to the policy. */ addRules(...rules: PolicyRule[]): this; /** * Builds the final policy. */ build(): Policy; } /** * Creates a new policy builder. * * @param name - Human-readable name for the policy * @param defaultReleaseType - Release type when no rule matches * * @example * ```ts * const policy = createPolicy('semver-strict', 'major') * .addRule(rule('removal').action('removed').returns('major')) * .addRule(rule('addition').action('added').returns('minor')) * .build(); * ``` * * @alpha */ export declare function createPolicy(name: string, defaultReleaseType: ReleaseType): PolicyBuilder; /** * Result of classifying a change with a policy. * * @remarks * This type extends ClassifiedChange which contains the full change data * plus the release type and matched rule info. The `change` property is * provided for backward compatibility but is effectively the same as `this`. * * New code should use ClassifiedChange directly. * * @alpha */ export interface ClassificationResult extends ClassifiedChange { /** * The original change (for backward compatibility). * @deprecated Access properties directly on the result instead. */ change: ApiChange; } /** * Applies a policy to classify a single change. * * @param change - The change to classify * @param policy - The policy to apply * @returns Classification result with release type and matched rule * * @alpha */ export declare function classifyChange(change: ApiChange, policy: Policy): ClassificationResult; /** * Applies a policy to classify multiple changes. * * @param changes - The changes to classify * @param policy - The policy to apply * @returns Array of classification results * * @alpha */ export declare function classifyChanges(changes: ApiChange[], policy: Policy): ClassificationResult[]; /** * Determines the overall release type from classification results. * Returns the highest severity: forbidden \> major \> minor \> patch \> none. * * @param results - Array of classified changes or classification results * @returns The highest severity release type * * @alpha */ export declare function determineOverallRelease(results: ReadonlyArray>): ReleaseType; //# sourceMappingURL=rule-builder.d.ts.map