@hyperfrontend/versioning/commitscommits/
Conventional commit parsing and models.
Overview
Parsing and models for the Conventional Commits specification. Uses state-machine tokenization for predictable O(n) performance.
Usage Examples
Parsing Commits
import { parseConventionalCommit, isConventionalCommit } from '@hyperfrontend/versioning'
// Parse a commit message
const commit = parseConventionalCommit('feat(api): add user endpoint')
console.log(commit.type) // 'feat'
console.log(commit.scope) // ['api']
console.log(commit.subject) // 'add user endpoint'
// Multi-scope headers produce multi-element arrays
const multi = parseConventionalCommit('feat(versioning,questions): add filter')
console.log(multi.scope) // ['versioning', 'questions']
// Scopeless commits produce an empty array
const scopeless = parseConventionalCommit('docs: update README')
console.log(scopeless.scope) // []
// Check format before parsing
if (isConventionalCommit(message)) {
const parsed = parseConventionalCommit(message)
}
Breaking Changes
import { parseConventionalCommit } from '@hyperfrontend/versioning'
// Breaking change via !
const breaking1 = parseConventionalCommit('feat!: remove deprecated API')
console.log(breaking1.breaking) // true
// Breaking change via footer
const breaking2 = parseConventionalCommit(`fix: update signature
BREAKING CHANGE: The return type is now a Promise`)
console.log(breaking2.breaking) // true
console.log(breaking2.breakingDescription) // 'The return type is now a Promise'
Body and Footers
import { parseConventionalCommit } from '@hyperfrontend/versioning'
const commit = parseConventionalCommit(`feat(core): add validation
This adds input validation to all public methods.
Invalid inputs now throw descriptive errors.
Refs: ABC-123
Closes #456`)
console.log(commit.body)
// 'This adds input validation to all public methods.\nInvalid inputs now throw descriptive errors.'
console.log(commit.footers)
// [
// { key: 'Refs', value: 'ABC-123', separator: ':' },
// { key: 'Closes', value: '456', separator: ' #' }
// ]
Creating Commits
import { createConventionalCommit, createCommitFooter } from '@hyperfrontend/versioning'
const commit = createConventionalCommit('feat', 'add feature', {
scope: ['api'],
body: 'Detailed description',
footers: [createCommitFooter('Refs', 'ABC-123'), createCommitFooter('Closes', '456', ' #')],
breaking: true,
})
console.log(commit.raw)
// 'feat(api)!: add feature\n\nDetailed description\n\nRefs: ABC-123\nCloses #456'
// Multi-scope headers
const multi = createConventionalCommit('feat', 'shared change', { scope: ['versioning', 'questions'] })
console.log(multi.raw) // 'feat(versioning,questions): shared change'
Semver Integration
import { parseConventionalCommit, getSemverBump } from '@hyperfrontend/versioning'
const commit = parseConventionalCommit('feat: add feature')
const bump = getSemverBump(commit.type, commit.breaking)
console.log(bump) // 'minor'
const breaking = parseConventionalCommit('fix!: change API')
const breakingBump = getSemverBump(breaking.type, breaking.breaking)
console.log(breakingBump) // 'major'
Conventional Commits Format
<type>[optional scope][!]: <description>
[optional body]
[optional footer(s)]
Header
- type: Describes the category of change (feat, fix, docs, etc.)
- scope: Optional context in parentheses. Supports multi-scope headers such as
feat(versioning,questions): ...; parsed into areadonly string[](empty when no scope is present). - !: Optional breaking change indicator
- description: Short summary of the change
Body
Free-form text separated from header by a blank line. Can span multiple paragraphs.
Footers
Token-based trailers in key: value or key #value format:
Refs: ABC-123
Fixes #456
BREAKING CHANGE: API signature changed
Security
- No regex: All parsing uses character-by-character iteration
- Input limits: 10KB maximum message length enforced
- ReDoS safe: No regular expressions means no catastrophic backtracking
See Also
- classify/ — Commit classification for changelog attribution
- changelog/ — Generates entries from commits
- flow/ — Analyzes commits to determine version bumps
- semver/ — Version bumping based on commit types
- Main README — Package overview and quick start
- ARCHITECTURE.md — Design principles and data flow
API Reference
ƒ Functions
Parameters
| Name | Type | Description |
|---|---|---|
§...matchers | unknown | Matchers to combine |
Returns
InfrastructureMatcherExample
Combining matchers with AND logic
const combined = allOf(
scopeMatcher(['deps']),
messageMatcher(['security'])
)Parameters
| Name | Type | Description |
|---|---|---|
§...matchers | unknown | Matchers to combine |
Returns
InfrastructureMatcherExample
Combining matchers with OR logic
const combined = anyOf(
scopeMatcher(['ci', 'build']),
messageMatcher(['[infra]']),
custom((ctx) => ctx.scope.some((s) => s.startsWith('tool-')))
)Combines scope-based matching with any custom matcher using OR logic. Path-based matching is handled separately via git queries.
Parameters
| Name | Type | Description |
|---|---|---|
§config | InfrastructureConfig | Infrastructure configuration |
Returns
InfrastructureMatcherExample
Building an infrastructure matcher
const matcher = buildInfrastructureMatcher({
scopes: ['ci', 'build'],
matcher: (ctx) => ctx.scope.some((s) => s.startsWith('tool-'))
})Implements the hybrid classification strategy:
- Check scope match (fast path)
- Check file touch (validation/catch-all)
- Check dependency touch (indirect)
- Fallback to excluded
Parameters
Returns
ClassifiedCommitExample
Classifying a single commit
const classified = classifyCommit(
{ commit: parsedCommit, raw: gitCommit },
{ projectScopes: ['versioning'], fileCommitHashes: new Set(['abc123']) }
)Parameters
Returns
ClassificationResultExample
Classifying multiple commits
const commits = [{ commit: parsedCommit, raw: gitCommit }]
const context = createClassificationContext(['auth'], fileHashes)
const result = classifyCommits(commits, context)
// => { commits: [...], included: [...], excluded: [...], summary: { total: 1, ... } }createClassificationContext(projectScopes: unknown, fileCommitHashes: ReadonlySet<string>, options?: ClassificationContextOptions): ClassificationContext
Parameters
Returns
ClassificationContextExample
Creating a classification context
const context = createClassificationContext(
['auth', 'lib-auth'],
new Set(['abc123', 'def456']),
{ excludeScopes: ['release'] }
)createClassifiedCommit(commit: ConventionalCommit, raw: GitCommit, source: CommitSource, options?: CreateClassifiedCommitOptions): ClassifiedCommit
Parameters
Returns
ClassifiedCommitExample
Creating a classified commit
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, ... }Returns
ClassificationSummaryExample
Creating an empty classification summary
const summary = createEmptyClassificationSummary()
// => { total: 0, included: 0, excluded: 0, bySource: { 'direct-scope': 0, ... } }Extracts scope from conventional commit message if present.
Parameters
Returns
InfrastructureMatchContextExample
Creating match context from a git commit
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' }Given a project named 'lib-versioning' with package '@hyperfrontend/versioning', this generates variations like:
- 'lib-versioning' (full project name)
- 'versioning' (without lib- prefix)
Parameters
| Name | Type | Description |
|---|---|---|
§options | DeriveProjectScopesOptions | Project identification options |
Returns
unknownExamples
Deriving scopes for a library project
deriveProjectScopes({ projectName: 'lib-versioning', packageName: '@hyperfrontend/versioning' })
// Returns: ['lib-versioning', 'versioning']Deriving scopes for an app project
deriveProjectScopes({ projectName: 'app-demo', packageName: 'demo-app' })
// Returns: ['app-demo', 'demo']evaluateInfrastructure(commit: GitCommit, matcher: InfrastructureMatcher, scope?: unknown): boolean
Parameters
Returns
booleanExample
Evaluating a commit against infrastructure matcher
const commit = { hash: 'abc123', subject: 'chore(ci): update workflow', message: '...' }
const ciMatcher = (ctx) => ctx.scope.includes('ci')
evaluateInfrastructure(commit, ciMatcher, ['ci'])
// => trueParameters
| Name | Type | Description |
|---|---|---|
§commits | unknown | Array of classified commits |
Returns
unknownExample
Extracting conventional commits
const classified = classifyCommits(commits, context)
const conventional = extractConventionalCommits(classified.included)
// => [{ type: 'feat', subject: 'add login', ... }]Parameters
| Name | Type | Description |
|---|---|---|
§commits | unknown | Array of classified commits |
Returns
unknownExample
Filtering for included commits
const classified = classifyCommits(commits, context)
const included = filterIncluded(classified.commits)
// => [{ commit, raw, source: 'direct-scope', include: true, ... }]Parameters
| Name | Type | Description |
|---|---|---|
§patterns | unknown | Patterns to search for in commit message (case-insensitive) |
Returns
InfrastructureMatcherExample
Matching message patterns
const matcher = messageMatcher(['[infra]', '[ci skip]'])Parameters
| Name | Type | Description |
|---|---|---|
§matcher | InfrastructureMatcher | Matcher to negate |
Returns
InfrastructureMatcherExample
Negating a matcher
const notRelease = not(scopeMatcher(['release']))A commit is excluded only when every scope entry matches an exclude scope. Commits with no scope entries are never considered excluded here.
Parameters
Returns
booleanExample
Checking if a scope should be excluded
scopeIsExcluded(['release'], ['release', 'deps'])
// => true
scopeIsExcluded(['auth'], ['release', 'deps'])
// => false
scopeIsExcluded([], ['release'])
// => false
scopeIsExcluded(['release', 'auth'], ['release'])
// => false (not every scope is excluded)Parameters
| Name | Type | Description |
|---|---|---|
§scopes | unknown | Scopes to match against (case-insensitive) |
Returns
InfrastructureMatcherExample
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)A commit matches the project when any of its scope entries matches a project scope. Empty commit scopes never match.
Parameters
Returns
booleanExample
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']) // falseParameters
| Name | Type | Description |
|---|---|---|
§prefixes | unknown | Scope prefixes to match (case-insensitive) |
Returns
InfrastructureMatcherExample
Matching prefixed scopes
const matcher = scopePrefixMatcher(['tool-', 'infra-'])
matcher({ scope: ['tool-package'], ... }) // true
matcher({ scope: ['lib-utils'], ... }) // falseParameters
| Name | Type | Description |
|---|---|---|
§pattern | RegExp | Regex pattern to test against scopes |
Returns
InfrastructureMatcherExample
Matching with regex pattern
const matcher = scopeRegexMatcher(/^(ci|build|tool)-.+/)For direct commits, the scope is removed (redundant in project changelog). For indirect commits, the scope is preserved (provides context).
Parameters
| Name | Type | Description |
|---|---|---|
§classified | ClassifiedCommit | Commit with classification metadata determining scope display |
Returns
ConventionalCommitExample
Converting classified commit for changelog
const classified = { commit: { type: 'feat', scope: ['auth'], subject: 'add login', ... }, source: 'direct-scope', preserveScope: false, ... }
toChangelogCommit(classified)
// => { type: 'feat', scope: [], subject: 'add login', ... }cancelled result with an optional error.Parameters
| Name | Type | Description |
|---|---|---|
§error? | Error | Cause of cancellation attached to the result (omit when the user aborted cleanly) |
Returns
StepResultExample
Cancelling after an executor throws
try { await executor() } catch (err) { return cancelled(err as Error) }type(scope)!: prefix and the supplied subject. Powers the live 72-character countdown shown during the subject prompt. The subject is passed separately (not read from
draft.subject) because the caller typically has not yet committed the in-flight input to the draft.Parameters
Returns
numberExample
Counting a header mid-type
countHeaderLength({ type: 'feat', scope: ['core'] }, 'add login')
// => 'feat(core): add login'.length === 21config (defaults filled in) and the effective step list — allowing bin wrappers to introspect before running.Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateAuthorSessionOptions | Optional overrides for steps and partial config (default: {}) |
Returns
AuthorSessionrunAuthorSessionExamples
Using the conventional preset and default config
const session = createAuthorSession()
await runAuthorSession(session)Overriding only the validate ruleset
createAuthorSession({ config: { validateRuleset: customRuleset } })Parameters
| Name | Type | Description |
|---|---|---|
§description? | string | Optional description of the breaking change |
Returns
BreakingChangeExample
Creating a breaking change from subject
createBreakingFromSubject('remove deprecated API')
// => { isBreaking: true, description: 'remove deprecated API', source: 'subject' }createConventionalCommit(type: string, subject: string, options?: CreateConventionalCommitOptions): ConventionalCommit
Parameters
Returns
ConventionalCommitExample
Creating conventional commits
createConventionalCommit('feat', 'add user authentication')
// => { type: 'feat', subject: 'add user authentication', scope: [], footers: [], breaking: false, raw: 'feat: add user authentication' }
createConventionalCommit('fix', 'resolve memory leak', { scope: ['core'], breaking: true })
// => { type: 'fix', subject: 'resolve memory leak', scope: ['core'], breaking: true, ... }
createConventionalCommit('feat', 'add x', { scope: ['versioning', 'questions'] })
// => { ..., scope: ['versioning', 'questions'], raw: 'feat(versioning,questions): add x' }Returns
BreakingChangeExample
Creating a non-breaking change
createNonBreaking()
// => { isBreaking: false, source: 'none' }Parameters
| Name | Type | Description |
|---|---|---|
§config | SessionConfig | Resolved session configuration |
Returns
SessionContextExample
Creating a bare context
createSessionContext(config)
// => { draft: {}, candidateScopes: [], defaultScope: undefined, config }scope step. Algorithm (matches locked decision D3):
- A single candidate wins outright.
- Otherwise, if one candidate's path is a prefix of every other
- Otherwise, there is no default.
Parameters
| Name | Type | Description |
|---|---|---|
§candidates | unknown | Scopes produced by discoverScopes |
Returns
stringExamples
Single owner wins
defaultScope([{ name: 'alpha', path: '/repo/libs/alpha' }])
// => 'alpha'Common ancestor wins
defaultScope([
{ name: 'root', path: '/repo' },
{ name: 'alpha', path: '/repo/libs/alpha' },
])
// => 'root'Disjoint ownership has no default
defaultScope([
{ name: 'alpha', path: '/repo/libs/alpha' },
{ name: 'beta', path: '/repo/libs/beta' },
])
// => undefinedproject.json / package.json, reads the name field, and returns the unique set as DiscoveredScope entries. Entries inside node_modules or .git are dropped before walking; the caller's scopeFilter is applied after collection.Parameters
Returns
unknownExample
Discovering scopes for a set of staged files
discoverScopes(['libs/versioning/src/a.ts', 'libs/questions/src/b.ts'], { cwd: '/repo' })
// => [
// { name: '@hyperfrontend/versioning', path: '/repo/libs/versioning' },
// { name: '@hyperfrontend/questions', path: '/repo/libs/questions' },
// ]done result.Returns
StepResultExample
Returning done from a step
async run(): Promise<StepResult> { return done() }.git/COMMIT_EDITMSG. Layout: header, blank line, body, blank line, footers. If
draft.breaking and draft.breakingDescription are set and no BREAKING CHANGE:-family footer is already present, one is synthesized at the top of the footer block so the preview mirrors what a compliant parser will read back.Parameters
| Name | Type | Description |
|---|---|---|
§draft | CommitDraft | Commit draft to render |
Returns
string\n separatorsExamples
Header only
formatCommitMessage({ type: 'feat', subject: 'add login' })
// => 'feat: add login'Multi-scope breaking change with closing footer
formatCommitMessage({
type: 'feat',
scope: ['versioning', 'questions'],
subject: 'add x',
breaking: true,
breakingDescription: 'removed Y',
footers: [{ key: 'Closes', value: '#1', separator: ':' }],
})
// => 'feat(versioning,questions)!: add x\n\nBREAKING CHANGE: removed Y\nCloses: #1'type(scope)!: subject) from a draft. Missing fields render as empty — this is intentional so the header can be displayed incrementally during authoring (e.g. before the subject has been typed). Scopes are comma-joined to match the parser's multi-scope format.
Parameters
| Name | Type | Description |
|---|---|---|
§draft | CommitDraft | Draft to render (may be partial) |
Returns
stringExamples
Rendering a complete header
formatHeader({ type: 'feat', scope: ['core'], subject: 'add login' })
// => 'feat(core): add login'Rendering a multi-scope breaking-change header
formatHeader({ type: 'feat', scope: ['versioning', 'questions'], subject: 'add x', breaking: true })
// => 'feat(versioning,questions)!: add x'Rendering a partially-built draft mid-session
formatHeader({ type: 'fix' })
// => 'fix: 'Parameters
Returns
"minor" | "patch" | "none" | "major"Example
Determining semver bump level
getSemverBump('feat', false)
// => 'minor'
getSemverBump('fix', true)
// => 'major'
getSemverBump('docs', false)
// => 'none'git diff --cached --name-only -z, splits on NUL, and filters out the trailing empty entry. Returns paths relative to the git repository root (same as git's own output).Parameters
| Name | Type | Description |
|---|---|---|
§options | StagedPathsOptions | Resolver options (cwd, optional timeout) |
Returns
unknownExample
Reading the current staging area
getStagedPaths({ cwd: '/repo' })
// => ['libs/versioning/src/commits/author/index.ts']goto result pointing at the supplied step id.Parameters
| Name | Type | Description |
|---|---|---|
§stepId | string | Target step id the runner should jump to |
Returns
StepResultExample
Sending the runner back to the `type` step
return goto('type')Parameters
| Name | Type | Description |
|---|---|---|
§message | string | The commit message to check |
Returns
booleanExample
Checking if a message is a conventional commit
isConventionalCommit('feat(auth): add OAuth login')
// => true
isConventionalCommit('WIP: still working on this')
// => false
isConventionalCommit('fix: resolve bug')
// => trueParameters
| Name | Type | Description |
|---|---|---|
§type | string | The commit type to check |
Returns
booleanExample
Checking for release-triggering types
isReleaseType('feat')
// => true
isReleaseType('chore')
// => falseParameters
| Name | Type | Description |
|---|---|---|
§type | string | The commit type to check |
Returns
unknownExample
Checking for standard commit types
isStandardType('feat')
// => true
isStandardType('custom')
// => falseoverridePathwins when set- Otherwise walk upward from
cwdlooking for one ofCONFIG_FILE_NAMES
.git, pnpm-workspace.yaml) is hit - Missing config is not an error —
configbecomes{}
Parameters
| Name | Type | Description |
|---|---|---|
§options | LoadCommitConfigOptions | Search inputs |
Returns
Promise<LoadedCommitConfig>Example
Auto-discovery from cwd
const { config, sourcePath } = await loadCommitConfig({ cwd: process.cwd() })Parameters
| Name | Type | Description |
|---|---|---|
§raw | string | Subject string as entered by the user |
Returns
stringExample
Stripping whitespace and a trailing period
normalizeSubject(' add login flow. ') // => 'add login flow'The body starts after the first blank line and continues until we encounter a footer (key: value or key #value pattern) or end of message.
Parameters
Returns
ParsedBodyExample
Parsing commit body
const lines = [
'feat: add login',
'',
'Implements OAuth flow.',
'',
'Refs: #123'
]
const result = parseBody(lines, 1)
// => { body: 'Implements OAuth flow.', endIndex: 4 }Parameters
| Name | Type | Description |
|---|---|---|
§message | string | The complete commit message |
Returns
ConventionalCommitExample
Parsing a complete conventional commit message
parseConventionalCommit('feat(auth): add login\n\nImplements OAuth.\n\nRefs: #123')
// => {
// type: 'feat',
// scope: ['auth'],
// subject: 'add login',
// body: 'Implements OAuth.',
// footers: [{ key: 'Refs', value: '#123', separator: ':' }],
// breaking: false,
// raw: '...'
// }Supports comma-separated multi-scope headers such as
feat(a,b): x which produce a multi-element scope array. Single-scope headers produce a one-element array, and scopeless headers produce an empty array.Parameters
| Name | Type | Description |
|---|---|---|
§line | string | The first line of the commit message |
Returns
ParsedHeaderExample
Parsing conventional commit headers
parseHeader('feat(auth): add OAuth login')
// => { type: 'feat', scope: ['auth'], subject: 'add OAuth login', breaking: false }
parseHeader('fix!: critical security patch')
// => { type: 'fix', scope: [], subject: 'critical security patch', breaking: true }
parseHeader('feat(versioning,questions): add searchable select')
// => { type: 'feat', scope: ['versioning', 'questions'], subject: 'add searchable select', breaking: false }keyword #number shape.Parameters
| Name | Type | Description |
|---|---|---|
§raw | string | Raw user input from the issues prompt |
Returns
unknownExample
Parsing a mixed references string
parseReferences('fixes #123, re #456')
// => [
// { key: 'Fixes', value: '123', separator: ' #' },
// { key: 'Re', value: '456', separator: ' #' },
// ]SessionConfig usable by the runner.Parameters
| Name | Type | Description |
|---|---|---|
§overrides | PartialSessionConfig | Partial config supplied by the caller (may be undefined) (default: {}) |
Returns
SessionConfigExample
Resolving with no overrides
resolveSessionConfig()
// => { types: DEFAULT_SESSION_TYPES, scopeOptional: false, ... }goto jumps, surfaces Ctrl-C cancellations as { status: 'cancelled' } outcomes, and — on success — returns the final formatted message alongside the committed status.Parameters
| Name | Type | Description |
|---|---|---|
§session | AuthorSession | Session description produced by createAuthorSession |
Returns
Promise<SessionOutcome>Example
Running a session to completion
const session = createAuthorSession({ config: { skipCommit: true } })
const outcome = await runAuthorSession(session)
// => { status: 'committed', message: 'feat: ...' }ConventionalCommit to a CommitDraft. Useful for editing a parsed commit inside an authoring session.Parameters
| Name | Type | Description |
|---|---|---|
§commit | ConventionalCommit | Parsed conventional commit |
Returns
CommitDraftExample
Promoting a parsed commit to an editable draft
toDraft(parseConventionalCommit('feat: add login'))
// => { type: 'feat', scope: [], subject: 'add login', footers: [], breaking: false }SessionType to a Choice usable by the select prompt.Parameters
| Name | Type | Description |
|---|---|---|
§type | SessionType | Session type entry to render as a selectable choice |
Returns
Choice<string>Example
Rendering the feat type as a select choice
typeToChoice({ name: 'feat', description: 'A new feature' })
// => { label: 'feat', value: 'feat', hint: 'A new feature' }Rules whose config is
'off' (or absent from the ruleset) are skipped. Unknown rule names in the ruleset are ignored — consumers extend the engine by calling validateCommitWithRules() and supplying additional rules.Parameters
Returns
ValidationResultExample
Validating a commit against a preset ruleset
validateCommit(parseConventionalCommit('feat: add login'), conventionalPreset)
// => { valid: true, warnings: [], errors: [] }cl bin and anywhere else validation must start from a string rather than a parsed commit.Parameters
Returns
ValidationResultExample
Validating a raw commit message
validateCommitMessage('feat: add login', conventionalPreset)
// => { valid: true, warnings: [], errors: [] }
validateCommitMessage('feat: added login', conventionalPreset).warnings
// => [{ level: 'warn', ruleName: 'imperative-mood', message: ... }]validateCommitWithRules(commit: ConventionalCommit, ruleset: Ruleset, rules: Readonly<Record<string, Rule<never>>>): ValidationResult
validateCommit that accepts a caller-supplied rule registry. Useful for consumers that want to add project-specific rules without forking the built-in set.Parameters
Returns
ValidationResultExample
Extending the built-in rules with a custom rule
const customRules = { ...BUILT_IN_RULES, 'no-wip': noWipRule }
validateCommitWithRules(commit, { 'no-wip': ['error'] }, customRules)◈ Interfaces
Properties
readonly dependencyCommitMap?:ReadonlyMap<string, ReadonlySet<string>>— readonly infrastructureCommitHashes?:ReadonlySet<string>— Contains the original commit data plus attribution information that determines inclusion and presentation in changelogs.
Properties
readonly dependencyPath?:unknown— Properties
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
Properties
readonly matcher?:InfrastructureMatcher— readonly paths?:unknown— readonly scopes?:unknown— Contains information about a commit that helps determine whether it should be classified as infrastructure-related.
runAuthorSession. Contains the fully resolved config and the ordered step list that will be executed.ConventionalCommit accumulated field-by-field during an interactive authoring session. Every field is optional so the formatter can render intermediate states (e.g. the header as it exists after only type has been picked).Properties
readonly breakingDescription?:string— BREAKING CHANGE: footer when none is already presentProperties
createAuthorSession — the caller may supply their own step list; anything missing is resolved against the defaults used by the conventional preset.discoverScopes.Properties
Properties
readonly wordlist?:Readonly<Record<string, string>>— DEFAULT_IMPERATIVE_WORDLIST when provided.loadCommitConfig.loadCommitConfig.ConventionalCommit and return zero or more violation messages. The engine applies severity from the ruleset config and aggregates RuleMessages across all rules.Properties
Properties
SessionConfig.scopeFilter.createAuthorSession) fills the gaps with defaults before handing the result to the runner.Properties
readonly headerMaxLength:number— readonly imperativeWordlist:Readonly<Record<string, string>>— readonly scopeFilter?:(context: ScopeFilterContext) => boolean— { path, name }readonly scopeOptional:boolean— scope step may be skipped even if no candidates are foundreadonly skipCommit:boolean— commit step is a no-op (session returns the message only)readonly stagedPathsProvider:StagedPathsProvider— git diff --cached --name-only -zdraft field accumulates the in-progress commit message; candidateScopes/defaultScope are filled by the resolve-scope step and consumed by later steps. config is the fully resolved SessionConfig shared across the session.Properties
runAuthorSession.type step's choice list.Properties
Properties
readonly cases:unknown— Properties
◆ Types
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"Returns true if the commit should be classified as infrastructure-related. Matchers are composable - combine them using
anyOf, allOf, and not.type InfrastructureMatcher = (context: InfrastructureMatchContext) => booleantype CommitExecutor = (message: string) => void | Promise<void>type CommitType = "feat" | "fix" | "docs" | "style" | "refactor" | "perf" | "test" | "build" | "ci" | "chore" | "revert" | stringcreateAuthorSession/loadCommitConfig.type PartialSessionConfig = Partial<SessionConfig>A bare
[level] entry disables the rule when level is 'off', or enables it with no options otherwise. The [level, options] form passes rule-specific options through to the rule's check() implementation.type RuleConfig = unknown | unknowntype RuleResult = unknown'off' by the engine.type Ruleset = Readonly<Record<string, RuleConfig>>type SessionStatus = Readonly<{ Cancelled: "cancelled"; Committed: "committed" }>type SessionStatus = Readonly<{ Cancelled: "cancelled"; Committed: "committed" }>type StagedPathsProvider = (options: StagedPathsProviderOptions) => unknowntype StepStatus = Readonly<{ Cancelled: "cancelled"; Done: "done"; Goto: "goto" }>type StepStatus = Readonly<{ Cancelled: "cancelled"; Done: "done"; Goto: "goto" }>type SubjectCase = "sentence-case" | "lower-case" | "upper-case" | "kebab-case" | "snake-case" | "start-case"● Variables
Matches: ci, cd, build, pipeline, workflow, actions
Combines CI, tooling, and tool-prefix matchers.
Matches: tooling, workspace, monorepo, nx, root
draft.body unset (the formatter then omits the body section).draft.breakingDescription and is eventually synthesized into a BREAKING CHANGE: footer by formatCommitMessage.config.skipCommit is true the step short-circuits so the caller receives the formatted message without touching the working tree.@commitlint/config-conventional's baseline. | Rule | Level | Options | | ------------------ | ----- | ------------------------ | | type-enum | error |
CONVENTIONAL_TYPES | | subject-empty | error | — | | scope-enum | off | — | | subject-case | off | — | | header-max-length | warn | { maxLength: 72 } | | imperative-mood | warn | — |createAuthorSession when the caller does not supply their own sequence. Matches decision D8: resolve-scope → type → scope → subject → body → breaking → issues → preview → commit.These represent repository-level or infrastructure changes that typically don't belong in individual project changelogs.
type step when the caller does not supply their own list. Each conventional type is paired with a short hint.type(scope)!: subject) exceeds the configured maximum length.fixes #123, re #456) and appends them to draft.footers using the # separator form. The step is additive: existing footers stay, and any breaking-change footer synthesized later by the formatter is unaffected.
type step while keeping the draft intact for round-trip editing. Validation warnings are printed before the prompt.Calls
config.stagedPathsProvider() → discoverScopes → defaultScope and writes the results into the context. Cancels the session when the staging area is empty (decision E1a).resolve-scope. Respects scopeOptional (cancels with an actionable error when no candidates exist and scope is required) and scopeMulti (multiselect vs. single-select).headerMaxLength is set, a live countdown re-renders on every keystroke (green → yellow → red as the remaining budget shrinks).ctx.draft.type (so a preview → cancel restart re-opens on the previous choice).