import { GitCommit } from '../../git/models'; import { ConventionalCommit } from '../models'; /** * Context provided to infrastructure matchers for decision making. * * Contains information about a commit that helps determine * whether it should be classified as infrastructure-related. */ interface InfrastructureMatchContext { /** The git commit being evaluated */ readonly commit: GitCommit; /** Parsed conventional scopes (empty array when the commit has no scope) */ readonly scope: readonly string[]; /** Commit message subject */ readonly subject: string; /** Full commit message */ readonly message: string; } /** * Infrastructure matcher function type. * * Returns true if the commit should be classified as infrastructure-related. * Matchers are composable - combine them using `anyOf`, `allOf`, and `not`. * * @example * // Simple scope matcher * const ciMatcher: InfrastructureMatcher = (ctx) => ctx.scope.includes('ci') * * @example * // Using factory functions * const matcher = anyOf( * scopeMatcher(['ci', 'build', 'tooling']), * pathMatcher(['tools/', '.github/']) * ) */ type InfrastructureMatcher = (context: InfrastructureMatchContext) => boolean; /** * Configuration for building infrastructure commit sets. * * Supports multiple detection methods that can be combined: * - Path-based: Commits touching specific file paths * - Scope-based: Commits with specific conventional scopes * - Custom matcher: User-provided matching logic */ interface InfrastructureConfig { /** * File/directory paths that indicate infrastructure changes. * Used to query git for commits touching these paths. * * @example ['tools/', '.github/workflows/', 'nx.json'] */ readonly paths?: readonly string[]; /** * Conventional commit scopes that indicate infrastructure. * Matched against parsed commit scope. * * @example ['ci', 'build', 'tooling', 'workspace'] */ readonly scopes?: readonly string[]; /** * Custom matcher function for complex logic. * Combined with paths/scopes using OR logic. * * @example (ctx) => ctx.message.includes('[infra]') */ readonly matcher?: InfrastructureMatcher; } /** * Creates a matcher that checks if any commit scope matches any of the given scopes. * * @param scopes - Scopes to match against (case-insensitive) * @returns Matcher that returns true if any scope element matches * * @example Matching against specific scopes * const matcher = scopeMatcher(['ci', 'build', 'tooling']) * matcher({ scope: ['CI'], ... }) // true * matcher({ scope: ['feat'], ... }) // false * matcher({ scope: ['feat', 'ci'], ... }) // true (any element matches) */ declare function scopeMatcher(scopes: readonly string[]): InfrastructureMatcher; /** * Creates a matcher that checks if any commit scope starts with any of the given prefixes. * * @param prefixes - Scope prefixes to match (case-insensitive) * @returns Matcher that returns true if any scope element starts with any prefix * * @example Matching prefixed scopes * const matcher = scopePrefixMatcher(['tool-', 'infra-']) * matcher({ scope: ['tool-package'], ... }) // true * matcher({ scope: ['lib-utils'], ... }) // false */ declare function scopePrefixMatcher(prefixes: readonly string[]): InfrastructureMatcher; /** * Creates a matcher that checks if commit message contains any of the given patterns. * * @param patterns - Patterns to search for in commit message (case-insensitive) * @returns Matcher that returns true if message contains any pattern * * @example Matching message patterns * const matcher = messageMatcher(['[infra]', '[ci skip]']) */ declare function messageMatcher(patterns: readonly string[]): InfrastructureMatcher; /** * Creates a matcher from a regex pattern tested against each scope element. * * @param pattern - Regex pattern to test against scopes * @returns Matcher that returns true if any scope element matches the regex * * @example Matching with regex pattern * const matcher = scopeRegexMatcher(/^(ci|build|tool)-.+/) */ declare function scopeRegexMatcher(pattern: RegExp): InfrastructureMatcher; /** * Combines matchers with OR logic - returns true if ANY matcher matches. * * @param matchers - Matchers to combine * @returns Combined matcher * * @example Combining matchers with OR logic * const combined = anyOf( * scopeMatcher(['ci', 'build']), * messageMatcher(['[infra]']), * custom((ctx) => ctx.scope.some((s) => s.startsWith('tool-'))) * ) */ declare function anyOf(...matchers: readonly InfrastructureMatcher[]): InfrastructureMatcher; /** * Combines matchers with AND logic - returns true if ALL matchers match. * * @param matchers - Matchers to combine * @returns Combined matcher * * @example Combining matchers with AND logic * const combined = allOf( * scopeMatcher(['deps']), * messageMatcher(['security']) * ) */ declare function allOf(...matchers: readonly InfrastructureMatcher[]): InfrastructureMatcher; /** * Negates a matcher - returns true if matcher returns false. * * @param matcher - Matcher to negate * @returns Negated matcher * * @example Negating a matcher * const notRelease = not(scopeMatcher(['release'])) */ declare function not(matcher: InfrastructureMatcher): InfrastructureMatcher; /** * Matches common CI/CD scopes. * * Matches: ci, cd, build, pipeline, workflow, actions */ declare const CI_SCOPE_MATCHER: InfrastructureMatcher; /** * Matches common tooling/workspace scopes. * * Matches: tooling, workspace, monorepo, nx, root */ declare const TOOLING_SCOPE_MATCHER: InfrastructureMatcher; /** * Matches tool-prefixed scopes (e.g., tool-package, tool-scripts). */ declare const TOOL_PREFIX_MATCHER: InfrastructureMatcher; /** * Combined matcher for common infrastructure patterns. * * Combines CI, tooling, and tool-prefix matchers. */ declare const DEFAULT_INFRA_SCOPE_MATCHER: InfrastructureMatcher; /** * Builds a combined matcher from infrastructure configuration. * * Combines scope-based matching with any custom matcher using OR logic. * Path-based matching is handled separately via git queries. * * @param config - Infrastructure configuration * @returns Combined matcher, or null if no matchers configured * * @example Building an infrastructure matcher * const matcher = buildInfrastructureMatcher({ * scopes: ['ci', 'build'], * matcher: (ctx) => ctx.scope.some((s) => s.startsWith('tool-')) * }) */ declare function buildInfrastructureMatcher(config: InfrastructureConfig): InfrastructureMatcher | null; /** * Creates match context from a git commit. * * Extracts scope from conventional commit message if present. * * @param commit - Git commit to create context for * @param scope - Pre-parsed scope array (optional, saves re-parsing) * @returns Match context for use with matchers * * @example Creating match context from a git commit * ```typescript * const commit = { hash: 'abc123', subject: 'chore(ci): update workflow', message: 'chore(ci): update workflow' } * createMatchContext(commit, ['ci']) * // => { commit, scope: ['ci'], subject: 'chore(ci): update workflow', message: 'chore(ci): update workflow' } * ``` */ declare function createMatchContext(commit: GitCommit, scope?: readonly string[]): InfrastructureMatchContext; /** * Evaluates a commit against an infrastructure matcher. * * @param commit - Git commit to evaluate * @param matcher - Matcher function to apply * @param scope - Pre-parsed scope (optional) * @returns True if commit matches infrastructure criteria * * @example Evaluating a commit against infrastructure matcher * ```typescript * const commit = { hash: 'abc123', subject: 'chore(ci): update workflow', message: '...' } * const ciMatcher = (ctx) => ctx.scope.includes('ci') * evaluateInfrastructure(commit, ciMatcher, ['ci']) * // => true * ``` */ declare function evaluateInfrastructure(commit: GitCommit, matcher: InfrastructureMatcher, scope?: readonly string[]): boolean; /** * Source of how a commit relates to a project. * * Classification determines whether a commit should appear in a * project's changelog and how its scope should be displayed. */ type CommitSource = 'direct-scope' | 'direct-file' | 'unscoped-file' | 'indirect-dependency' | 'indirect-infra' | 'unscoped-global' | 'excluded'; /** * A commit with classification metadata for changelog generation. * * Contains the original commit data plus attribution information * that determines inclusion and presentation in changelogs. */ interface ClassifiedCommit { /** The parsed conventional commit */ readonly commit: ConventionalCommit; /** The raw git commit (for hash, date, etc.) */ readonly raw: GitCommit; /** How this commit relates to the project */ readonly source: CommitSource; /** Whether to include this commit in changelog */ readonly include: boolean; /** Whether to preserve scope in changelog output */ readonly preserveScope: boolean; /** Files in this project touched by the commit (if applicable) */ readonly touchedFiles?: readonly string[]; /** Dependency chain if indirect (e.g., ['lib-utils'] for lib-app → lib-utils) */ readonly dependencyPath?: readonly string[]; } /** * Input for classification - a parsed commit with its raw git data. */ interface CommitWithRaw { /** The parsed conventional commit */ readonly commit: ConventionalCommit; /** The raw git commit data */ readonly raw: GitCommit; } /** * Classification context containing project and workspace info. */ interface ClassificationContext { /** Scopes that should be considered direct matches */ readonly projectScopes: readonly string[]; /** Set of commit hashes that touched project files */ readonly fileCommitHashes: ReadonlySet; /** Map of dependency name to set of commit hashes touching that dependency */ readonly dependencyCommitMap?: ReadonlyMap>; /** * Set of commit hashes that touched infrastructure paths. * These commits will be classified as 'indirect-infra'. */ readonly infrastructureCommitHashes?: ReadonlySet; /** Scopes to always exclude */ readonly excludeScopes?: readonly string[]; /** Additional scopes to include as direct */ readonly includeScopes?: readonly string[]; } /** * Result of classifying multiple commits. */ interface ClassificationResult { /** All classified commits */ readonly commits: readonly ClassifiedCommit[]; /** Commits to include in changelog */ readonly included: readonly ClassifiedCommit[]; /** Commits excluded from changelog */ readonly excluded: readonly ClassifiedCommit[]; /** Summary statistics */ readonly summary: ClassificationSummary; } /** * Summary statistics from classification. */ interface ClassificationSummary { /** Total commits processed */ readonly total: number; /** Commits included in changelog */ readonly included: number; /** Commits excluded from changelog */ readonly excluded: number; /** Breakdown by source type */ readonly bySource: Readonly>; } /** * Creates an empty classification summary. * * @returns A new ClassificationSummary with all counts at zero * * @example Creating an empty classification summary * ```typescript * const summary = createEmptyClassificationSummary() * // => { total: 0, included: 0, excluded: 0, bySource: { 'direct-scope': 0, ... } } * ``` */ declare function createEmptyClassificationSummary(): ClassificationSummary; /** * Optional metadata that can be attached when constructing a {@link ClassifiedCommit}. */ type CreateClassifiedCommitOptions = { /** Files in the project modified by this commit */ readonly touchedFiles?: readonly string[]; /** Chain of dependencies leading to indirect inclusion */ readonly dependencyPath?: readonly string[]; }; /** * Creates a classified commit. * * @param commit - The parsed conventional commit * @param raw - The raw git commit * @param source - How the commit relates to the project * @param options - Additional classification options * @param options.touchedFiles - Files in the project modified by this commit * @param options.dependencyPath - Chain of dependencies leading to indirect inclusion * @returns A new ClassifiedCommit object * * @example Creating a classified commit * ```typescript * const commit = { type: 'feat', subject: 'add feature', footers: [], breaking: false, raw: '...' } * const raw = { hash: 'abc123', subject: 'feat: add feature', message: '...' } * const classified = createClassifiedCommit(commit, raw, 'direct-scope') * // => { commit, raw, source: 'direct-scope', include: true, preserveScope: false, ... } * ``` */ declare function createClassifiedCommit(commit: ConventionalCommit, raw: GitCommit, source: CommitSource, options?: CreateClassifiedCommitOptions): ClassifiedCommit; /** * Options for deriving project scopes. */ interface DeriveProjectScopesOptions { /** The project name (e.g., 'lib-versioning') */ readonly projectName: string; /** The npm package name (e.g., '@hyperfrontend/versioning') */ readonly packageName?: string; /** Additional scopes to include */ readonly additionalScopes?: readonly string[]; /** * Project name prefixes to strip for scope matching. * * @default DEFAULT_PROJECT_PREFIXES */ readonly prefixes?: readonly string[]; } /** * Derives all scope variations that should match a project. * * Given a project named 'lib-versioning' with package '@hyperfrontend/versioning', * this generates variations like: * - 'lib-versioning' (full project name) * - 'versioning' (without lib- prefix) * * @param options - Project identification options * @returns Array of scope strings that match this project * * @example Deriving scopes for a library project * deriveProjectScopes({ projectName: 'lib-versioning', packageName: '@hyperfrontend/versioning' }) * // Returns: ['lib-versioning', 'versioning'] * * @example Deriving scopes for an app project * deriveProjectScopes({ projectName: 'app-demo', packageName: 'demo-app' }) * // Returns: ['app-demo', 'demo'] */ declare function deriveProjectScopes(options: DeriveProjectScopesOptions): readonly string[]; /** * Default project name prefixes that can be stripped for scope matching. */ declare const DEFAULT_PROJECT_PREFIXES: readonly ["lib-", "app-", "e2e-", "tool-", "plugin-", "feature-", "package-"]; /** * Checks if any element of a commit's scope list matches any of the project scopes. * * A commit matches the project when **any** of its scope entries matches a * project scope. Empty commit scopes never match. * * @param commitScopes - Scopes from a conventional commit * @param projectScopes - Array of scopes that match the project * @returns True if at least one commit scope matches the project * * @example Matching scope to project * scopeMatchesProject(['versioning'], ['lib-versioning', 'versioning']) // true * scopeMatchesProject(['logging'], ['lib-versioning', 'versioning']) // false * scopeMatchesProject(['versioning', 'questions'], ['lib-questions']) // true * scopeMatchesProject([], ['lib-versioning']) // false */ declare function scopeMatchesProject(commitScopes: readonly string[], projectScopes: readonly string[]): boolean; /** * Checks if all scopes of a commit are in the exclude list. * * A commit is excluded only when **every** scope entry matches an exclude * scope. Commits with no scope entries are never considered excluded here. * * @param commitScopes - Scopes from a conventional commit * @param excludeScopes - Array of scopes to exclude * @returns True if every commit scope is in the exclude list * * @example Checking if a scope should be excluded * ```typescript * scopeIsExcluded(['release'], ['release', 'deps']) * // => true * * scopeIsExcluded(['auth'], ['release', 'deps']) * // => false * * scopeIsExcluded([], ['release']) * // => false * * scopeIsExcluded(['release', 'auth'], ['release']) * // => false (not every scope is excluded) * ``` */ declare function scopeIsExcluded(commitScopes: readonly string[], excludeScopes: readonly string[]): boolean; /** * Default scopes to exclude from changelogs. * * These represent repository-level or infrastructure changes * that typically don't belong in individual project changelogs. */ declare const DEFAULT_EXCLUDE_SCOPES: readonly string[]; /** * Classifies a single commit against a project. * * Implements the hybrid classification strategy: * 1. Check scope match (fast path) * 2. Check file touch (validation/catch-all) * 3. Check dependency touch (indirect) * 4. Fallback to excluded * * @param input - The commit to classify * @param context - Classification context with project info * @returns Classified commit with source attribution * * @example Classifying a single commit * const classified = classifyCommit( * { commit: parsedCommit, raw: gitCommit }, * { projectScopes: ['versioning'], fileCommitHashes: new Set(['abc123']) } * ) */ declare function classifyCommit(input: CommitWithRaw, context: ClassificationContext): ClassifiedCommit; /** * Classifies multiple commits against a project. * * @param commits - Array of commits to classify * @param context - Classification context with project info * @returns Classification result with all commits and summary * * @example Classifying multiple commits * ```typescript * const commits = [{ commit: parsedCommit, raw: gitCommit }] * const context = createClassificationContext(['auth'], fileHashes) * const result = classifyCommits(commits, context) * // => { commits: [...], included: [...], excluded: [...], summary: { total: 1, ... } } * ``` */ declare function classifyCommits(commits: readonly CommitWithRaw[], context: ClassificationContext): ClassificationResult; /** * Optional inputs for {@link createClassificationContext}. */ type ClassificationContextOptions = { /** Map of dependency names to commit hashes touching them */ readonly dependencyCommitMap?: ReadonlyMap>; /** Set of commit hashes touching infrastructure paths */ readonly infrastructureCommitHashes?: ReadonlySet; /** Scopes to explicitly exclude from classification */ readonly excludeScopes?: readonly string[]; /** Additional scopes to include as direct matches */ readonly includeScopes?: readonly string[]; }; /** * Creates a classification context from common inputs. * * @param projectScopes - Scopes that match the project * @param fileCommitHashes - Set of commit hashes that touched project files * @param options - Additional context options * @param options.dependencyCommitMap - Map of dependency names to commit hashes touching them * @param options.infrastructureCommitHashes - Set of commit hashes touching infrastructure paths * @param options.excludeScopes - Scopes to explicitly exclude from classification * @param options.includeScopes - Additional scopes to include as direct matches * @returns A ClassificationContext object * * @example Creating a classification context * ```typescript * const context = createClassificationContext( * ['auth', 'lib-auth'], * new Set(['abc123', 'def456']), * { excludeScopes: ['release'] } * ) * ``` */ declare function createClassificationContext(projectScopes: readonly string[], fileCommitHashes: ReadonlySet, options?: ClassificationContextOptions): ClassificationContext; /** * Filters an array of classified commits to only included ones. * * @param commits - Array of classified commits * @returns Only commits marked for inclusion * * @example Filtering for included commits * ```typescript * const classified = classifyCommits(commits, context) * const included = filterIncluded(classified.commits) * // => [{ commit, raw, source: 'direct-scope', include: true, ... }] * ``` */ declare function filterIncluded(commits: readonly ClassifiedCommit[]): readonly ClassifiedCommit[]; /** * Extracts conventional commits from classified commits for changelog generation. * * @param commits - Array of classified commits * @returns Array of conventional commits * * @example Extracting conventional commits * ```typescript * const classified = classifyCommits(commits, context) * const conventional = extractConventionalCommits(classified.included) * // => [{ type: 'feat', subject: 'add login', ... }] * ``` */ declare function extractConventionalCommits(commits: readonly ClassifiedCommit[]): readonly ConventionalCommit[]; /** * Creates a modified conventional commit with scope handling based on classification. * * For direct commits, the scope is removed (redundant in project changelog). * For indirect commits, the scope is preserved (provides context). * * @param classified - Commit with classification metadata determining scope display * @returns A conventional commit with appropriate scope handling * * @example Converting classified commit for changelog * ```typescript * const classified = { commit: { type: 'feat', scope: ['auth'], subject: 'add login', ... }, source: 'direct-scope', preserveScope: false, ... } * toChangelogCommit(classified) * // => { type: 'feat', scope: [], subject: 'add login', ... } * ``` */ declare function toChangelogCommit(classified: ClassifiedCommit): ConventionalCommit; export { CI_SCOPE_MATCHER, DEFAULT_EXCLUDE_SCOPES, DEFAULT_INFRA_SCOPE_MATCHER, DEFAULT_PROJECT_PREFIXES, TOOLING_SCOPE_MATCHER, TOOL_PREFIX_MATCHER, allOf, anyOf, buildInfrastructureMatcher, classifyCommit, classifyCommits, createClassificationContext, createClassifiedCommit, createEmptyClassificationSummary, createMatchContext, deriveProjectScopes, evaluateInfrastructure, extractConventionalCommits, filterIncluded, messageMatcher, not, scopeIsExcluded, scopeMatcher, scopeMatchesProject, scopePrefixMatcher, scopeRegexMatcher, toChangelogCommit }; export type { ClassificationContext, ClassificationResult, ClassificationSummary, ClassifiedCommit, CommitSource, CommitWithRaw, DeriveProjectScopesOptions, InfrastructureConfig, InfrastructureMatchContext, InfrastructureMatcher };