import { Choice } from '../../_dependencies/@hyperfrontend/questions/index.js'; import { Ruleset } from '../validate'; import { CommitDraft } from '../format'; import { CommitFooter } from '../models'; /** Conventional-commit type entry shown in the `type` step's choice list. */ interface SessionType { /** Bare identifier (e.g. `feat`, `fix`) */ readonly name: string; /** Short description rendered as a prompt hint */ readonly description?: string; } /** Signature for the scope filter passed in `SessionConfig.scopeFilter`. */ interface ScopeFilterContext { /** Absolute path to the discovered project root */ readonly path: string; /** `name` field read from `project.json` (or `package.json` as fallback) */ readonly name: string; } /** Signature for executing the final git commit. */ type CommitExecutor = (message: string) => void | Promise; /** Options passed to the `StagedPathsProvider`. */ interface StagedPathsProviderOptions { /** Working directory used for git operations and scope discovery */ readonly cwd: string; } /** Signature for resolving the staged file paths the session operates on. */ type StagedPathsProvider = (options: StagedPathsProviderOptions) => readonly string[]; /** * Fully resolved authoring session configuration. Consumers typically supply a * partial shape: the config loader (or `createAuthorSession`) fills the gaps * with defaults before handing the result to the runner. */ interface SessionConfig { /** Commit type enum shown in the `type` step */ readonly types: readonly SessionType[]; /** Optional filter applied to discovered scopes; receives `{ path, name }` */ readonly scopeFilter?: (context: ScopeFilterContext) => boolean; /** When true, the `scope` step may be skipped even if no candidates are found */ readonly scopeOptional: boolean; /** When true, the `scope` step collects multiple scopes via multiselect */ readonly scopeMulti: boolean; /** Resolver for the staged file list; defaults to `git diff --cached --name-only -z` */ readonly stagedPathsProvider: StagedPathsProvider; /** Working directory used for git operations and scope discovery */ readonly cwd: string; /** Maximum header length used by the subject countdown (null disables) */ readonly headerMaxLength: number | null; /** Wordlist used by the imperative-mood rule in inline validation */ readonly imperativeWordlist: Readonly>; /** When true, the final `commit` step is a no-op (session returns the message only) */ readonly skipCommit: boolean; /** Overrides the default `git commit` executor */ readonly commitExecutor?: CommitExecutor; /** Validation ruleset used for inline and preview-time warnings */ readonly validateRuleset: Ruleset; /** Optional stream override passed to every prompt in the session */ readonly input?: NodeJS.ReadStream; /** Optional stream override passed to every prompt in the session */ readonly output?: NodeJS.WriteStream; } /** Partial shape accepted by `createAuthorSession`/`loadCommitConfig`. */ type PartialSessionConfig = Partial; /** * Maps a `SessionType` to a `Choice` usable by the `select` prompt. * * @param type - Session type entry to render as a selectable choice * @returns Choice with the type name as both label and value, plus an optional hint * * @example Rendering the feat type as a select choice * ```typescript * typeToChoice({ name: 'feat', description: 'A new feature' }) * // => { label: 'feat', value: 'feat', hint: 'A new feature' } * ``` */ declare function typeToChoice(type: SessionType): Choice; /** Supported config file names, searched in this order per directory. */ declare const CONFIG_FILE_NAMES: readonly string[]; /** Inputs accepted by `loadCommitConfig`. */ interface LoadCommitConfigOptions { /** Directory to start the upward search from */ readonly cwd: string; /** Explicit override path from `--config `, resolved against `cwd` when relative */ readonly overridePath?: string; } /** Return shape of `loadCommitConfig`. */ interface LoadedCommitConfig { /** Resolved configuration (empty object when no config was found) */ readonly config: PartialSessionConfig; /** Absolute path of the config file that was loaded (undefined when nothing was found) */ readonly sourcePath?: string; } /** * Loads the user's commit config. Resolution: * 1. `overridePath` wins when set * 2. Otherwise walk upward from `cwd` looking for one of `CONFIG_FILE_NAMES` * until a workspace boundary marker (`.git`, `pnpm-workspace.yaml`) is hit * 3. Missing config is not an error: `config` becomes `{}` * * @param options - Search inputs * @returns Loaded partial config plus the file it came from (if any) * * @example Auto-discovery from cwd * ```typescript * const { config, sourcePath } = await loadCommitConfig({ cwd: process.cwd() }) * ``` */ declare function loadCommitConfig(options: LoadCommitConfigOptions): Promise; /** * Mutable container threaded through every step. The `draft` 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. */ interface SessionContext { /** In-progress commit draft accumulated as steps execute */ draft: CommitDraft; /** Scopes discovered from the staged file set (may be empty) */ candidateScopes: readonly string[]; /** Default scope pre-selected by the `scope` step (undefined = no default) */ defaultScope: string | undefined; /** Resolved configuration for the session */ readonly config: SessionConfig; } /** * Creates a fresh session context seeded with an empty draft. * * @param config - Resolved session configuration * @returns Session context ready for the first step * * @example Creating a bare context * ```typescript * createSessionContext(config) * // => { draft: {}, candidateScopes: [], defaultScope: undefined, config } * ``` */ declare function createSessionContext(config: SessionConfig): SessionContext; /** Session outcome status constants. */ declare const SessionStatus: Readonly<{ /** Session ran to completion and (when `skipCommit === false`) created a commit */ readonly Committed: "committed"; /** Session terminated early (user Ctrl-C, empty staging refusal, commit failure) */ readonly Cancelled: "cancelled"; }>; /** Session outcome status values. */ type SessionStatus = (typeof SessionStatus)[keyof typeof SessionStatus]; /** Terminal outcome returned by `runAuthorSession`. */ interface SessionOutcome { /** Status tag describing how the session ended */ readonly status: SessionStatus; /** Formatted commit message (populated whenever the draft reached the preview step) */ readonly message?: string; /** Error attached when the session cancelled due to an explicit failure */ readonly error?: Error; } /** Result status constants returned by steps. */ declare const StepStatus: Readonly<{ /** Step completed normally; runner advances to the next step */ readonly Done: "done"; /** Step aborted the session (error surfaces via `error` field) */ readonly Cancelled: "cancelled"; /** Step requested a jump back to a named step */ readonly Goto: "goto"; }>; /** Result status values returned by steps. */ type StepStatus = (typeof StepStatus)[keyof typeof StepStatus]; /** Outcome produced by a step implementation. */ interface StepResult { /** Status tag used by the runner to decide how to proceed */ readonly status: StepStatus; /** Target step id when `status === 'goto'` */ readonly gotoStepId?: string; /** Error attached when `status === 'cancelled'` */ readonly error?: Error; } /** A named step in an authoring session. */ interface Step { /** Stable identifier used by `goto` and for debugging */ readonly id: string; /** Runs the step against the session context; may mutate `ctx.draft` */ run(ctx: SessionContext): Promise; } /** * Helper for building a `done` result. * * @returns Step result that advances the runner to the next step * * @example Returning done from a step * ```typescript * async run(): Promise { return done() } * ``` */ declare function done(): StepResult; /** * Helper for building a `cancelled` result with an optional error. * * @param error - Cause of cancellation attached to the result (omit when the user aborted cleanly) * @returns Step result that stops the session * * @example Cancelling after an executor throws * ```typescript * try { await executor() } catch (err) { return cancelled(err as Error) } * ``` */ declare function cancelled(error?: Error): StepResult; /** * Helper for building a `goto` result pointing at the supplied step id. * * @param stepId - Target step id the runner should jump to * @returns Step result that redirects the runner to the named step * * @example Sending the runner back to the `type` step * ```typescript * return goto('type') * ``` */ declare function goto(stepId: string): StepResult; /** Options controlling `discoverScopes`. */ interface DiscoverScopesOptions { /** Optional caller-provided filter; applied after built-in exclusions */ readonly scopeFilter?: (context: ScopeFilterContext) => boolean; /** Working directory that staged paths are resolved against */ readonly cwd: string; } /** Single discovered scope with the project root it came from. */ interface DiscoveredScope { /** Scope identifier (the project's `name` field) */ readonly name: string; /** Absolute path to the owning project root */ readonly path: string; } /** * Resolves each staged path to its owning project by walking upward to the * nearest `project.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. * * @param stagedPaths - Paths emitted by the staged-paths provider (relative or absolute) * @param options - Filter + cwd configuration * @returns Unique scopes in staging order (first appearance wins) * * @example Discovering scopes for a set of staged files * ```typescript * 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' }, * // ] * ``` */ declare function discoverScopes(stagedPaths: readonly string[], options: DiscoverScopesOptions): readonly DiscoveredScope[]; /** Options accepted by the default staged-paths provider. */ interface StagedPathsOptions { /** Working directory used as the git command cwd */ readonly cwd: string; /** Timeout in milliseconds (default: 30000) */ readonly timeout?: number; } /** * Default staged-paths provider. Resolves the repository root via * `git rev-parse --show-toplevel`, reads `git diff --cached --name-only -z`, * and returns each staged path as an absolute path anchored at that root. * * Git emits staged paths relative to the repository root no matter which * directory the command runs from, so anchoring here keeps downstream * project-root discovery correct when the session cwd is a subdirectory. * * @param options - Resolver options (cwd, optional timeout) * @returns Absolute staged file paths * * @example Reading the current staging area from a subdirectory * ```typescript * getStagedPaths({ cwd: '/repo/apps/demo' }) * // => ['/repo/libs/versioning/src/commits/author/index.ts'] * ``` */ declare function getStagedPaths(options: StagedPathsOptions): readonly string[]; /** * Inputs accepted by `createAuthorSession`: the caller may supply their own * step list; anything missing is resolved against the defaults used by the * conventional preset. */ interface CreateAuthorSessionOptions { /** Override for the step sequence; defaults to `conventionalPreset` */ readonly steps?: readonly Step[]; /** Partial configuration overlaid on the built-in defaults */ readonly config?: PartialSessionConfig; } /** * Prepared session ready to be handed to `runAuthorSession`. Contains the * fully resolved config and the ordered step list that will be executed. */ interface AuthorSession { /** Ordered step list the runner will iterate */ readonly steps: readonly Step[]; /** Fully resolved session configuration */ readonly config: SessionConfig; } /** * Prepares an authoring session without executing it. The returned object * carries a resolved `config` (defaults filled in) and the effective step * list, allowing bin wrappers to introspect before running. * * @param options - Optional overrides for steps and partial config * @returns Session description consumable by `runAuthorSession` * * @example Using the conventional preset and default config * ```typescript * const session = createAuthorSession() * await runAuthorSession(session) * ``` * * @example Overriding only the validate ruleset * ```typescript * createAuthorSession({ config: { validateRuleset: customRuleset } }) * ``` */ declare function createAuthorSession(options?: CreateAuthorSessionOptions): AuthorSession; /** * Default ordered step list used by `createAuthorSession` when the caller * does not supply their own sequence. Matches decision D8: * `resolve-scope → type → scope → subject → body → breaking → issues → preview → commit`. */ declare const conventionalPreset: readonly Step[]; /** * Computes the default scope to pre-select in the `scope` step. * * Algorithm (matches locked decision D3): * 1. A single candidate wins outright. * 2. Otherwise, if one candidate's path is a prefix of every other * candidate's path, that nearest common ancestor wins. * 3. Otherwise, there is no default. * * @param candidates - Scopes produced by `discoverScopes` * @returns Scope name to pre-select, or undefined when no default is resolvable * * @example Single owner wins * ```typescript * defaultScope([{ name: 'alpha', path: '/repo/libs/alpha' }]) * // => 'alpha' * ``` * * @example Common ancestor wins * ```typescript * defaultScope([ * { name: 'root', path: '/repo' }, * { name: 'alpha', path: '/repo/libs/alpha' }, * ]) * // => 'root' * ``` * * @example Disjoint ownership has no default * ```typescript * defaultScope([ * { name: 'alpha', path: '/repo/libs/alpha' }, * { name: 'beta', path: '/repo/libs/beta' }, * ]) * // => undefined * ``` */ declare function defaultScope(candidates: readonly DiscoveredScope[]): string | undefined; /** * Default type enum rendered by the `type` step when the caller does not * supply their own list. Each conventional type is paired with a short hint. */ declare const DEFAULT_SESSION_TYPES: readonly SessionType[]; /** * Overlays a partial config over the built-in defaults, producing a fully * populated `SessionConfig` usable by the runner. * * @param overrides - Partial config supplied by the caller (may be undefined) * @returns Fully resolved configuration * * @example Resolving with no overrides * ```typescript * resolveSessionConfig() * // => { types: DEFAULT_SESSION_TYPES, scopeOptional: false, ... } * ``` */ declare function resolveSessionConfig(overrides?: PartialSessionConfig): SessionConfig; /** * Executes the session step-by-step. Handles `goto` jumps, surfaces Ctrl-C * cancellations as `{ status: 'cancelled' }` outcomes, and (on success) * returns the final formatted message alongside the `committed` status. * * @param session - Session description produced by `createAuthorSession` * @returns Terminal outcome describing how the session ended * * @example Running a session to completion * ```typescript * const session = createAuthorSession({ config: { skipCommit: true } }) * const outcome = await runAuthorSession(session) * // => { status: 'committed', message: 'feat: ...' } * ``` */ declare function runAuthorSession(session: AuthorSession): Promise; /** * Step that prompts for an optional commit body. Empty input leaves * `draft.body` unset (the formatter then omits the body section). */ declare const bodyStep: Step; /** * Step that asks whether the commit introduces a breaking change. A positive * answer follows up with a description prompt; the description is written to * `draft.breakingDescription` and is eventually synthesized into a * `BREAKING CHANGE:` footer by `formatCommitMessage`. */ declare const breakingStep: Step; /** * Step that renders the draft one last time and hands the message to the * configured commit executor. When `config.skipCommit` is true the step * short-circuits so the caller receives the formatted message without * touching the working tree. */ declare const commitStep: Step; /** * Step that optionally collects issue references (e.g. `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. */ declare const issuesStep: Step; /** * Parses a free-form references string into issue footers. Accepts commas, * semicolons, or whitespace between entries; ignores tokens that don't match * the `keyword #number` shape. * * @param raw - Raw user input from the issues prompt * @returns Footers matching each recognized reference * * @example Parsing a mixed references string * ```typescript * parseReferences('fixes #123, re #456') * // => [ * // { key: 'Fixes', value: '123', separator: ' #' }, * // { key: 'Re', value: '456', separator: ' #' }, * // ] * ``` */ declare function parseReferences(raw: string): readonly CommitFooter[]; /** Step id the preview jumps back to when the user rejects the draft. */ declare const PREVIEW_RESTART_STEP_ID = "type"; /** * Step that renders the accumulated draft as the final commit message and * asks the user to confirm. A negative answer jumps back to the `type` step * while keeping the draft intact for round-trip editing. Validation warnings * are printed before the prompt. */ declare const previewStep: Step; /** Error thrown to the runner when the staging area is empty. */ declare const EMPTY_STAGING_MESSAGE = "No staged changes; stage something first."; /** * Non-prompt step that computes the candidate scope list and default scope. * * Calls `config.stagedPathsProvider()` → `discoverScopes` → `defaultScope` and * writes the results into the context. Cancels the session when the staging * area is empty (decision E1a). * * @example Populating the context from staged files * ```typescript * await resolveScopeStep.run(ctx) * // ctx.candidateScopes === [...discovered] * // ctx.defaultScope === 'alpha' | undefined * ``` */ declare const resolveScopeStep: Step; /** Error thrown when the scope step cannot proceed because no candidates were found. */ declare const NO_SCOPE_CANDIDATES_MESSAGE = "No scope candidates were discovered. Either stage a file under a project or set scopeOptional: true."; /** * Step that prompts the user to pick the commit scope(s) from the candidate * list discovered by `resolve-scope`. Respects `scopeOptional` (cancels with * an actionable error when no candidates exist and scope is required) and * `scopeMulti` (multiselect vs. single-select). */ declare const scopeStep: Step; /** * Step that prompts for the commit subject. Strips the trailing period, trims * edges, and enforces a non-empty subject. When `headerMaxLength` is set, a * live countdown re-renders on every keystroke (green → yellow → red as the * remaining budget shrinks). */ declare const subjectStep: Step; /** * Applies subject normalisation rules (decision D10): trim edges and strip a * single trailing period. * * @param raw - Subject string as entered by the user * @returns Normalized subject * * @example Stripping whitespace and a trailing period * ```typescript * normalizeSubject(' add login flow. ') // => 'add login flow' * ``` */ declare function normalizeSubject(raw: string): string; /** * Step that prompts the user for the commit type. Uses the searchable select * prompt and seeds the cursor from any existing `ctx.draft.type` (so a preview * → cancel restart re-opens on the previous choice). */ declare const typeStep: Step; export { CONFIG_FILE_NAMES, DEFAULT_SESSION_TYPES, EMPTY_STAGING_MESSAGE, NO_SCOPE_CANDIDATES_MESSAGE, PREVIEW_RESTART_STEP_ID, SessionStatus, SessionStatus as SessionStatusValues, StepStatus, StepStatus as StepStatusValues, bodyStep, breakingStep, cancelled, commitStep, conventionalPreset, createAuthorSession, createSessionContext, defaultScope, discoverScopes, done, getStagedPaths, goto, issuesStep, loadCommitConfig, normalizeSubject, parseReferences, previewStep, resolveScopeStep, resolveSessionConfig, runAuthorSession, scopeStep, subjectStep, typeStep, typeToChoice }; export type { AuthorSession, CommitExecutor, CreateAuthorSessionOptions, DiscoverScopesOptions, DiscoveredScope, LoadCommitConfigOptions, LoadedCommitConfig, PartialSessionConfig, ScopeFilterContext, SessionConfig, SessionContext, SessionOutcome, SessionType, StagedPathsOptions, StagedPathsProvider, Step, StepResult };