@synmux/claude-commit 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Library entry point for `claude-commit`.
3
+ *
4
+ * Re-exports the building blocks so the commit-message pipeline can be used
5
+ * programmatically. The CLI lives in `bin/cco.ts` (`src/cli.ts`).
6
+ */
7
+ export { generateCommit } from "./src/generate.ts";
8
+ export type { GenerateOptions, GenerateProgress, GenerateResult, IgnoreStats, LowPriorityStats, OllamaContextWindow, } from "./src/generate.ts";
9
+ export { runClaudePrompt, runPrompt } from "./src/agent.ts";
10
+ export type { RunPromptOptions } from "./src/agent.ts";
11
+ export { probeOllamaContext, resolveOllamaContext, resolveOllamaHost, runOllamaPrompt, } from "./src/ollama.ts";
12
+ export { DEFAULT_OLLAMA_CONTEXT, DEFAULT_OLLAMA_CONTEXT_TOKENS, DEFAULT_OLLAMA_HOST, isOllamaModel, OLLAMA_PREFIX, parseModelRef, } from "./src/models.ts";
13
+ export type { ModelProvider, ModelRef } from "./src/models.ts";
14
+ export { applyIgnorePatterns, diffPaths, partitionDiff, sectionPaths, splitDiff, } from "./src/diff.ts";
15
+ export type { DiffPartition, IgnoreResult } from "./src/diff.ts";
16
+ export { createPathMatcher, matchesPathPatterns } from "./src/paths.ts";
17
+ export type { PathMatcher } from "./src/paths.ts";
18
+ export { DEFAULT_CONFIG, loadFileConfig, resolveConfig, mergeConfig, mergePartial, sanitizePartial, } from "./src/config.ts";
19
+ export { buildSummarySystem, buildSummaryUser, buildFinalSystem, buildFinalUser, buildFilenamesUser, parseOptions, cleanMessage, } from "./src/prompts.ts";
20
+ export * as git from "./src/git.ts";
21
+ export { ClaudeCommitError } from "./src/errors.ts";
22
+ export type { ChangePriority, Config, DiffSummary, ModelConfig, OllamaConfig, PartialConfig, ModelResult, FileChange, } from "./src/types.ts";
@@ -0,0 +1,93 @@
1
+ /**
2
+ * The model-call layer: {@link runPrompt} turns one prompt into one text
3
+ * completion, routing to whichever backend the model name asks for.
4
+ *
5
+ * A bare model name goes to Claude, through the Agent SDK, below. An
6
+ * `ollama:`-prefixed one goes to `src/ollama.ts` instead. Both return the
7
+ * same {@link ModelResult}, so the pipeline in `src/generate.ts` - and the
8
+ * injectable runner its tests use - never learns which provider ran.
9
+ *
10
+ * The Claude path, in detail:
11
+ *
12
+ * The Agent SDK spawns a bundled `claude` binary, so authentication follows
13
+ * Claude Code's own resolution order over the environment we hand it. By
14
+ * default the API credential variables (`ANTHROPIC_API_KEY` /
15
+ * `ANTHROPIC_AUTH_TOKEN`) are stripped from that environment, forcing the
16
+ * `claude login` subscription session so the cost is bundled with Claude Code
17
+ * usage; the `allowApiKey` config option passes them through for explicit
18
+ * pay-as-you-go billing. Every request runs fully isolated from the user's
19
+ * Claude Code configuration - no tools, skills, MCP servers, plugins, or
20
+ * settings (see {@link buildQueryOptions}) - so the request contains nothing
21
+ * beyond the prompt we build.
22
+ */
23
+ import { type Options } from "@anthropic-ai/claude-agent-sdk";
24
+ import type { ModelResult, RunPromptOptions } from "./types.ts";
25
+ export type { RunPromptOptions } from "./types.ts";
26
+ export declare const GATED_CREDENTIAL_VARS: readonly ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"];
27
+ /**
28
+ * Names of the gated credential variables present in `env`. An empty string
29
+ * counts as present, since presence alone perturbs credential resolution.
30
+ */
31
+ export declare function presentCredentialVars(env: Record<string, string | undefined>): string[];
32
+ export interface SubprocessEnvOptions {
33
+ /** Environment to derive the subprocess environment from (usually `process.env`). */
34
+ baseEnv: Record<string, string | undefined>;
35
+ /**
36
+ * Pass API credential variables through instead of stripping them.
37
+ * Defaults to false so the gate fails safe when a caller omits it.
38
+ */
39
+ allowApiKey?: boolean;
40
+ /** Sampling temperature to inject via `CLAUDE_CODE_EXTRA_BODY`, preserving any existing extra body. */
41
+ temperature?: number;
42
+ }
43
+ /**
44
+ * Build the environment for the Claude Agent SDK subprocess, or return
45
+ * `undefined` when the parent environment can be inherited unchanged.
46
+ *
47
+ * Unless `allowApiKey` is set, API credential variables are removed so the
48
+ * spawned `claude` binary always authenticates with the user's subscription
49
+ * session - an exported `ANTHROPIC_API_KEY` must never silently switch
50
+ * billing to pay-as-you-go.
51
+ */
52
+ export declare function buildSubprocessEnv(opts: SubprocessEnvOptions): Record<string, string | undefined> | undefined;
53
+ /**
54
+ * Build the Agent SDK options for one isolated, single-turn text completion.
55
+ *
56
+ * Isolation is layered because the SDK gates each context source separately:
57
+ *
58
+ * - `settingSources: []` disables settings files and `CLAUDE.md` - and only
59
+ * those. It does NOT stop MCP servers or skills from loading.
60
+ * - `mcpServers: {}` + `strictMcpConfig: true` ignore every MCP server
61
+ * configured in `~/.claude.json`, project `.mcp.json`, and plugins.
62
+ * - `skills: []` disables skill discovery, which the CLI otherwise performs
63
+ * even when the `skills` option is omitted entirely.
64
+ * - `tools: []` and `plugins: []` drop all built-in tools and plugins.
65
+ *
66
+ * Omitting any of these lets the user's global Claude Code configuration
67
+ * (MCP tool schemas, skill listings - easily hundreds of thousands of tokens
68
+ * on a busy setup) into every request; with all of them set, live probes
69
+ * measure ~170 input tokens per request. Historical note: the 2026-07
70
+ * "Prompt is too long" failures were ultimately caused by underestimating
71
+ * the token density of armored diff content (see `estimateDiffTokens`), not
72
+ * by this leak - the isolation is hygiene and cost control, not the fix for
73
+ * that bug.
74
+ */
75
+ export declare function buildQueryOptions(opts: RunPromptOptions, subprocessEnv?: Record<string, string | undefined>): Options;
76
+ /**
77
+ * Run a single prompt against a Claude model via the Agent SDK.
78
+ *
79
+ * Throws {@link ClaudeCommitError} on any model/authentication/quota failure.
80
+ */
81
+ export declare function runClaudePrompt(prompt: string, opts: RunPromptOptions): Promise<ModelResult>;
82
+ /**
83
+ * Run a single prompt against whichever provider `opts.model` names, and
84
+ * return its text response.
85
+ *
86
+ * This is the single seam every caller uses; `generate.ts` accepts a
87
+ * replacement of exactly this shape so the pipeline can be tested without a
88
+ * model of either kind.
89
+ *
90
+ * Throws {@link ClaudeCommitError} on any model, authentication, transport
91
+ * or quota failure.
92
+ */
93
+ export declare function runPrompt(prompt: string, opts: RunPromptOptions): Promise<ModelResult>;
@@ -0,0 +1,36 @@
1
+ import type { Config, PartialConfig } from "./types.ts";
2
+ export declare const DEFAULT_CONFIG: Config;
3
+ /**
4
+ * The user-level config directory, `$XDG_CONFIG_HOME/claude-commit` (falling back
5
+ * to `~/.config/claude-commit`). Per the XDG Base Directory spec, `XDG_CONFIG_HOME`
6
+ * is honoured only when it is set to an absolute path.
7
+ */
8
+ export declare function globalConfigDir(env?: Record<string, string | undefined>): string;
9
+ /**
10
+ * Deep-ish merge of a partial config over a base config: `models` and
11
+ * `ollama` are merged key by key; the path lists (`lowPriorityPaths`,
12
+ * `ignore`) are replaced whole - a higher layer's list wins outright, so a
13
+ * project can drop a global pattern - and copied so the result never
14
+ * aliases the base's array.
15
+ */
16
+ export declare function mergeConfig(base: Config, override: PartialConfig): Config;
17
+ /** Validate and normalize a parsed partial config, ignoring unknown keys. */
18
+ export declare function sanitizePartial(raw: unknown): PartialConfig;
19
+ /**
20
+ * Load and merge the file-based configuration layers that sit below CLI flags,
21
+ * lowest first: the global user config, then `package.json`'s `claude-commit` key
22
+ * at the repo root, then the nearest project config file (searched from `cwd` up
23
+ * to `repoRoot`). An explicit `configPath` short-circuits the project-file
24
+ * discovery; the global and `package.json` layers still apply beneath it. `env`
25
+ * supplies `XDG_CONFIG_HOME` for locating the global config (defaults to
26
+ * `process.env`).
27
+ */
28
+ export declare function loadFileConfig(cwd: string, repoRoot: string, configPath?: string, env?: Record<string, string | undefined>): Promise<PartialConfig>;
29
+ /**
30
+ * Merge two partial configs: `models` and `ollama` are merged key by key;
31
+ * every other key, including both path lists, is taken whole from the
32
+ * override when present.
33
+ */
34
+ export declare function mergePartial(base: PartialConfig, override: PartialConfig): PartialConfig;
35
+ /** Produce a fully-resolved config from file config and CLI-flag overrides. */
36
+ export declare function resolveConfig(fileConfig: PartialConfig, flagConfig: PartialConfig): Config;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Splitting a unified git diff into chunks that fit a character budget, and
3
+ * partitioning it by path priority.
4
+ *
5
+ * The packer is structure-aware: it prefers to break on file boundaries, then
6
+ * on hunk (`@@`) boundaries, and only falls back to raw line splitting for a
7
+ * single hunk that is itself larger than the budget. When a file section is
8
+ * split, its header (`diff --git ... / --- / +++`) is repeated at the top of
9
+ * every piece so each chunk remains a self-contained, interpretable diff.
10
+ *
11
+ * `applyIgnorePatterns` drops whole file sections outright (see `ignore` in
12
+ * the config) and `partitionDiff` sorts what remains into a primary and a
13
+ * low-priority diff (see `lowPriorityPaths`), both using the paths
14
+ * `sectionPaths` recovers from each section's header lines.
15
+ */
16
+ import type { PathMatcher } from "./paths.ts";
17
+ /**
18
+ * Split a unified diff into chunks no larger than `maxChars` characters.
19
+ *
20
+ * Returns an empty array for an empty diff, and a single-element array when the
21
+ * whole diff already fits.
22
+ */
23
+ export declare function splitDiff(diff: string, maxChars: number): string[];
24
+ /**
25
+ * Replace each run of opaque (armored/encoded) lines with a single marker
26
+ * line. The marker keeps the surrounding diff structure interpretable and
27
+ * tells the summary model what was elided, so it can still report that an
28
+ * encrypted file changed - without paying ~1 token per character to send
29
+ * ciphertext the model cannot read anyway.
30
+ */
31
+ export declare function redactOpaqueRuns(diff: string): string;
32
+ /**
33
+ * The staged diff split by path priority. `primary` drives the commit
34
+ * message; `lowPriority` holds the sections whose every path matched a
35
+ * `lowPriorityPaths` pattern. Either may be `""`. The counts make the
36
+ * matcher observable (`--verbose`): a pattern that matches nothing and one
37
+ * that matches everything (and is therefore promoted) produce the same
38
+ * message otherwise.
39
+ */
40
+ export interface DiffPartition {
41
+ primary: string;
42
+ lowPriority: string;
43
+ /** File sections whose paths all matched. */
44
+ matchedFiles: number;
45
+ /** File sections with at least one recognisable path. */
46
+ totalFiles: number;
47
+ /** Every file matched, so the low-priority sections were promoted to primary. */
48
+ promoted: boolean;
49
+ }
50
+ /**
51
+ * The repository-relative paths a file section touches: one for an ordinary
52
+ * change, two for a rename or copy. Paths are read from the `---`/`+++`
53
+ * marker lines and `rename`/`copy from`/`to` lines in the section header
54
+ * (before the first hunk, so a removed line that happens to start with
55
+ * `-- ` is never mistaken for a marker), falling back to the
56
+ * `diff --git a/X b/Y` line for sections that have none. Returns `[]` for
57
+ * content that is not a file section at all.
58
+ */
59
+ export declare function sectionPaths(section: string): string[];
60
+ /**
61
+ * Unique repository-relative filenames touched by a diff, in encounter
62
+ * order. Includes both paths of renames and copies. Uses the same header
63
+ * parser as ignore/priority matching, so hunk contents cannot become names.
64
+ */
65
+ export declare function diffPaths(diff: string): string[];
66
+ /**
67
+ * Sort a diff's file sections into a primary and a low-priority diff.
68
+ *
69
+ * A section is low priority only when it names at least one path and every
70
+ * path it names matches - so a rename into or out of a low-priority area
71
+ * stays primary, as does anything whose path cannot be recognised. Sections
72
+ * keep their original order within each partition and are joined back with
73
+ * newlines, so each partition is itself a valid unified diff.
74
+ *
75
+ * When nothing is primary, the low-priority sections are promoted: with no
76
+ * other change to yield to, they *are* the change and should be described
77
+ * in full, exactly as if no patterns were configured.
78
+ */
79
+ export declare function partitionDiff(diff: string, isLowPriority: PathMatcher): DiffPartition;
80
+ /** What `ignore` removed from a diff. */
81
+ export interface IgnoreResult {
82
+ /** The diff with every ignored file section removed. May be `""`. */
83
+ diff: string;
84
+ /** File sections dropped because all of their paths matched. */
85
+ ignoredFiles: number;
86
+ /** File sections in the original diff with at least one recognisable path. */
87
+ totalFiles: number;
88
+ }
89
+ /**
90
+ * Drop the file sections whose every path matches, returning the rest.
91
+ *
92
+ * The rule is `partitionDiff`'s: a section is removed only when it names at
93
+ * least one path and all of them match, so a rename out of an ignored
94
+ * directory - which is news - survives, as does anything whose path cannot
95
+ * be read. Surviving sections keep their order and are joined with
96
+ * newlines, so the result is itself a valid unified diff.
97
+ *
98
+ * This runs before everything else in the pipeline, so ignored content is
99
+ * never chunked, never sent, and never paid for.
100
+ */
101
+ export declare function applyIgnorePatterns(diff: string, isIgnored: PathMatcher): IgnoreResult;
102
+ /**
103
+ * Split a diff so that every chunk's *classified token estimate* fits
104
+ * `maxTokens`.
105
+ *
106
+ * `splitDiff` budgets in characters, but token density varies wildly by
107
+ * content: prose and code sit near the configured `charsPerToken` (~3.5)
108
+ * while base64/armor lines measure near 1 char/token. Sizing every chunk
109
+ * with one blended ratio lets an armor-heavy region overflow, so after an
110
+ * initial blended-density split, any chunk still over budget is re-split
111
+ * using its own (denser) ratio until everything fits or no further split is
112
+ * possible. An unsplittable oversized chunk is kept - the overflow retry in
113
+ * the generation pipeline is the backstop for that case.
114
+ */
115
+ export declare function splitDiffToFit(diff: string, maxTokens: number, charsPerToken: number): string[];
@@ -1,8 +1,7 @@
1
1
  /** Error type for user-facing, expected failures (printed without a stack trace). */
2
- export class ClaudeCommitError extends Error {
3
- override name = "ClaudeCommitError";
2
+ export declare class ClaudeCommitError extends Error {
3
+ name: string;
4
4
  }
5
-
6
5
  /**
7
6
  * True when `error` is the backend rejecting a request for exceeding the
8
7
  * model's context window. Matched on message text because the Agent SDK
@@ -10,6 +9,4 @@ export class ClaudeCommitError extends Error {
10
9
  * re-split-and-retry: the rejection happens before the model runs, so it is
11
10
  * not billed.
12
11
  */
13
- export function isPromptTooLongError(error: unknown): boolean {
14
- return error instanceof Error && /prompt is too long/i.test(error.message);
15
- }
12
+ export declare function isPromptTooLongError(error: unknown): boolean;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * The commit-message pipeline:
3
+ *
4
+ * diff ──ignore──▶ ──partition──▶ primary diff, low-priority diff
5
+ * ──split──▶ [chunk, chunk, ...] ──summary model──▶ [summary, ...]
6
+ * ──final model──▶ commit message(s)
7
+ *
8
+ * The `ignore` patterns run first and remove file sections outright, so
9
+ * ignored content is never chunked, never sent and never paid for. Those
10
+ * files are still committed - `ignore` governs what the model reads, not
11
+ * what git stages - but when it matches *everything* there is nothing left
12
+ * to describe and the run stops rather than inventing a message.
13
+ *
14
+ * The remaining diff is partitioned by the configured `lowPriorityPaths`: file
15
+ * sections under those paths (generated docs, lockfiles, ...) form a
16
+ * low-priority partition that is summarised after, and more briefly than,
17
+ * the primary one, and the final model is told which is which so the
18
+ * subject line describes the primary changes. When every file is low
19
+ * priority the partition is promoted and the run is identical to one with
20
+ * no patterns configured.
21
+ *
22
+ * The summary model (default `sonnet`) reads each diff chunk and writes a
23
+ * factual summary; chunks are sized by a content-classified token estimate
24
+ * (`splitDiffToFit`) so each request fits the model's context window, and a
25
+ * chunk the backend still rejects as too long is re-split with a halved
26
+ * budget and retried - the rejection happens before the model runs and is
27
+ * not billed, so the API acts as the final arbiter of token counts. The
28
+ * final model (default `sonnet`) turns the summaries into the commit
29
+ * message(s), applying the configured formatting rules.
30
+ * With filenamesOnly, the summary stage is skipped entirely and the final
31
+ * model receives only paths from the filtered, priority-grouped diff.
32
+ */
33
+ import { runPrompt } from "./agent.ts";
34
+ import { resolveOllamaContext } from "./ollama.ts";
35
+ import type { Config, DiffSummary } from "./types.ts";
36
+ export interface GenerateProgress {
37
+ /** Called when a new phase of work begins (for spinner labels). */
38
+ onPhase?: (label: string) => void;
39
+ /** Receives streamed text of the final message as it is produced. */
40
+ onText?: (delta: string) => void;
41
+ }
42
+ export interface GenerateOptions {
43
+ /** Number of candidate messages to produce (interactive mode uses > 1). */
44
+ count?: number;
45
+ progress?: GenerateProgress;
46
+ abortController?: AbortController;
47
+ /**
48
+ * Model runner used for every prompt; injectable so tests can exercise the
49
+ * pipeline (including overflow retries) without real model calls.
50
+ * Defaults to {@link runPrompt}.
51
+ */
52
+ runner?: typeof runPrompt;
53
+ /**
54
+ * Resolves an `ollama:` model's context window, called once per model
55
+ * per run before any chunk is sized; injectable so tests can exercise an
56
+ * `"auto"` configuration without a server. Defaults to
57
+ * {@link resolveOllamaContext}.
58
+ */
59
+ resolveOllamaContext?: typeof resolveOllamaContext;
60
+ }
61
+ /** The context window one Ollama model ran with during this run. */
62
+ export interface OllamaContextWindow {
63
+ /** The model string as configured, prefix included. */
64
+ model: string;
65
+ tokens: number;
66
+ /** Whether the number was configured or chosen by the server (`"auto"`). */
67
+ source: "config" | "auto";
68
+ }
69
+ /** How the `ignore` patterns applied to this diff (for `--verbose`). */
70
+ export interface IgnoreStats {
71
+ /** File sections dropped before any model saw them. */
72
+ ignoredFiles: number;
73
+ /** File sections in the staged diff with a recognisable path. */
74
+ totalFiles: number;
75
+ }
76
+ /** How the `lowPriorityPaths` patterns applied to this diff (for `--verbose`). */
77
+ export interface LowPriorityStats {
78
+ /** File sections whose paths all matched a pattern. */
79
+ matchedFiles: number;
80
+ /** File sections in the diff with a recognisable path. */
81
+ totalFiles: number;
82
+ /** Every file matched, so the changes were treated as primary after all. */
83
+ promoted: boolean;
84
+ }
85
+ export interface GenerateResult {
86
+ /** Candidate commit messages (length 1 in non-interactive mode). */
87
+ messages: string[];
88
+ /** Intermediate summaries, primary first. Empty when filenamesOnly is enabled. */
89
+ summaries: DiffSummary[];
90
+ /** Number of diff chunks the summary stage processed, across both partitions. */
91
+ chunkCount: number;
92
+ /** Total cost across all model calls, in USD. */
93
+ costUsd: number;
94
+ /** How the low-priority patterns applied to this diff. */
95
+ lowPriority: LowPriorityStats;
96
+ /** How the ignore patterns applied to this diff. */
97
+ ignored: IgnoreStats;
98
+ /** The context window each Ollama model ran with, in order of first use. */
99
+ ollamaContexts: OllamaContextWindow[];
100
+ }
101
+ /** Run the full pipeline over a staged diff. */
102
+ export declare function generateCommit(diff: string, config: Config, options?: GenerateOptions): Promise<GenerateResult>;
103
+ /**
104
+ * The error for a commit whose every changed file matched `ignore`.
105
+ *
106
+ * There is no sensible fallback here. Describing the ignored files anyway
107
+ * would contradict the directive the user wrote; committing an empty or
108
+ * invented message would be worse. Naming the directive and the count makes
109
+ * the cause obvious, because the alternative - a run that mysteriously
110
+ * reports no staged changes when `git status` plainly disagrees - is the
111
+ * kind of bug people spend an afternoon on.
112
+ */
113
+ export declare function describeFullyIgnored(stats: IgnoreStats): string;
@@ -0,0 +1,31 @@
1
+ import { ClaudeCommitError } from "./errors.ts";
2
+ import type { FileChange } from "./types.ts";
3
+ /**
4
+ * A git command failed. Subclasses {@link ClaudeCommitError} so the CLI prints
5
+ * it as a clean, user-facing error rather than a stack trace.
6
+ */
7
+ export declare class GitError extends ClaudeCommitError {
8
+ name: string;
9
+ }
10
+ /** True if the current working directory is inside a git work tree. */
11
+ export declare function isGitRepo(): Promise<boolean>;
12
+ /** Absolute path to the repository root. */
13
+ export declare function getRepoRoot(): Promise<string>;
14
+ /** The unified diff of staged changes (`git diff --cached`), repository-root-relative. */
15
+ export declare function getStagedDiff(): Promise<string>;
16
+ /** Parsed list of staged files with their status codes. */
17
+ export declare function getStagedFiles(): Promise<FileChange[]>;
18
+ /** Stage every change in the work tree (`git add -A`). */
19
+ export declare function stageAll(): Promise<void>;
20
+ /** A short one-line stat summary of staged changes (for display). */
21
+ export declare function getStagedStat(): Promise<string>;
22
+ /**
23
+ * Create a commit with the given message. The message is piped to
24
+ * `git commit -F -` over stdin, so arbitrary content (leading dashes, multiple
25
+ * lines, special characters) is handled safely - and nothing touches disk, so
26
+ * there is no temp file to be raced or read by another user. Git's own
27
+ * stdout summary is discarded: the CLI prints its own confirmation.
28
+ */
29
+ export declare function commit(message: string): Promise<void>;
30
+ /** The current branch name (or `HEAD` when detached). */
31
+ export declare function getCurrentBranch(): Promise<string>;
@@ -0,0 +1,42 @@
1
+ /** Marks a model name as belonging to an Ollama server. Case-insensitive. */
2
+ export declare const OLLAMA_PREFIX = "ollama:";
3
+ /** Which backend serves a model. */
4
+ export type ModelProvider = "claude" | "ollama";
5
+ /** A model string resolved into a provider and the name that provider expects. */
6
+ export interface ModelRef {
7
+ provider: ModelProvider;
8
+ /** The model name to send to the provider, with any cco prefix removed. */
9
+ name: string;
10
+ }
11
+ /**
12
+ * Default Ollama base URL, used when neither the config nor `$OLLAMA_HOST`
13
+ * names one. This is Ollama's own default listen address.
14
+ */
15
+ export declare const DEFAULT_OLLAMA_HOST = "http://localhost:11434";
16
+ /**
17
+ * Default `ollama.context`: ask the server what window it would run the
18
+ * model with on this machine, rather than guess (see `probeOllamaContext`
19
+ * in `src/ollama.ts`). Ollama's choice is made from available VRAM and
20
+ * capped at the model's trained maximum, so it is the largest window the
21
+ * server believes it can actually load.
22
+ */
23
+ export declare const DEFAULT_OLLAMA_CONTEXT: "auto";
24
+ /**
25
+ * The window assumed for an `ollama:` model when nothing better is known:
26
+ * the synchronous fallback in `contextWindowTokens` for callers that size
27
+ * chunks without first resolving the context. The pipeline never relies on
28
+ * it - it resolves `"auto"` to a real number before sizing - so this only
29
+ * matters to direct library use. 32768 is Ollama's middle VRAM tier.
30
+ */
31
+ export declare const DEFAULT_OLLAMA_CONTEXT_TOKENS = 32768;
32
+ /** Whether `model` names an Ollama model (i.e. carries the `ollama:` prefix). */
33
+ export declare function isOllamaModel(model: string): boolean;
34
+ /**
35
+ * Resolve a configured model string into its provider and provider-side name.
36
+ *
37
+ * Throws {@link ClaudeCommitError} for a name that no provider could serve:
38
+ * an empty string, or an `ollama:` prefix with nothing after it.
39
+ */
40
+ export declare function parseModelRef(model: string): ModelRef;
41
+ /** A model string as it should appear in an error or a `--verbose` line. */
42
+ export declare function describeModel(model: string): string;
@@ -0,0 +1,89 @@
1
+ import type { ModelResult, OllamaConfig, RunPromptOptions } from "./types.ts";
2
+ /** Ollama settings with every default filled in; the context may still be `"auto"`. */
3
+ export interface ResolvedOllama {
4
+ host: string;
5
+ context: number | "auto";
6
+ keepAlive: string | number | null;
7
+ }
8
+ /** {@link ResolvedOllama} after `"auto"` has been turned into a number. */
9
+ export interface OllamaRequestSettings {
10
+ host: string;
11
+ contextTokens: number;
12
+ keepAlive: string | number | null;
13
+ }
14
+ /**
15
+ * Normalise a base URL: add a scheme to a bare `host:port` and drop any
16
+ * trailing slash. Ollama's own `OLLAMA_HOST` convention allows the bare
17
+ * form, so `127.0.0.1:11434` has to mean what a user expects it to.
18
+ */
19
+ export declare function normaliseOllamaHost(host: string): string;
20
+ /**
21
+ * The Ollama base URL for this run: the configured `ollama.host`, else
22
+ * `$OLLAMA_HOST`, else {@link DEFAULT_OLLAMA_HOST}.
23
+ */
24
+ export declare function resolveOllamaHost(configured?: string, env?: Record<string, string | undefined>): string;
25
+ /**
26
+ * Fill in defaults for any Ollama setting the config left out. A missing
27
+ * or unusable `context` becomes `"auto"`; turning that into a number is
28
+ * {@link resolveOllamaContext}'s job, because it takes a round trip.
29
+ */
30
+ export declare function resolveOllamaConfig(config: Partial<OllamaConfig> | undefined, env?: Record<string, string | undefined>): ResolvedOllama;
31
+ /**
32
+ * Ask Ollama what context window it would run `model` with on this machine.
33
+ *
34
+ * Two calls. The first is a chat request with no messages, which loads the
35
+ * model (a no-op if it is already resident) *without* a `num_ctx` - so the
36
+ * server applies its own choice, made from available VRAM (4k / 32k / 256k
37
+ * tiers, capped at the model's trained maximum). The second reads that
38
+ * choice back from `/api/ps`, which reports the window each loaded model is
39
+ * actually running with. The load was going to happen on the first real
40
+ * request anyway, so the only added cost is the `ps` round trip.
41
+ *
42
+ * This is deliberately not `/api/show`'s `context_length`, which is the
43
+ * *trained* maximum regardless of hardware - 131072 for a model this
44
+ * machine may only be able to run at 32768. The number the server picked
45
+ * is the one it can actually load.
46
+ */
47
+ export declare function probeOllamaContext(model: string, settings: {
48
+ host: string;
49
+ keepAlive: string | number | null;
50
+ }, signal?: AbortSignal): Promise<number>;
51
+ /**
52
+ * The context window to use for `model`: the configured number, or the
53
+ * server's own choice when the config says `"auto"` (see
54
+ * {@link probeOllamaContext}). Callers that make several requests to the
55
+ * same model should resolve once and reuse the result.
56
+ */
57
+ export declare function resolveOllamaContext(model: string, config: Partial<OllamaConfig> | undefined, signal?: AbortSignal): Promise<number>;
58
+ /** The body of a native `/api/chat` request. */
59
+ export interface OllamaChatRequest {
60
+ model: string;
61
+ messages: Array<{
62
+ role: "system" | "user";
63
+ content: string;
64
+ }>;
65
+ stream: boolean;
66
+ format?: Record<string, unknown>;
67
+ keep_alive?: string | number;
68
+ options: Record<string, unknown>;
69
+ }
70
+ /**
71
+ * Build the `/api/chat` body for one prompt.
72
+ *
73
+ * Two things are deliberate. Sampling parameters go inside `options` - at the
74
+ * top level Ollama accepts and silently ignores them, so a misplaced
75
+ * `temperature` would look like a model that refuses to vary. And `think` is
76
+ * never sent at all: models disagree on whether reasoning can be switched
77
+ * off (gpt-oss cannot, and takes only a level; Granite uses its own field
78
+ * entirely), so asking is a needless way to earn a 400. Any `thinking` that
79
+ * comes back is dropped on the floor instead.
80
+ */
81
+ export declare function buildChatRequest(prompt: string, opts: RunPromptOptions, settings: OllamaRequestSettings): OllamaChatRequest;
82
+ /**
83
+ * Run a single prompt against an Ollama model and return its response.
84
+ *
85
+ * Throws {@link ClaudeCommitError} on any transport, model or truncation
86
+ * failure. `costUsd` is always zero: local inference is not billed, so a
87
+ * mixed-provider run's reported cost is exactly its Claude half.
88
+ */
89
+ export declare function runOllamaPrompt(prompt: string, opts: RunPromptOptions): Promise<ModelResult>;
@@ -0,0 +1,9 @@
1
+ /** A predicate over repository-relative paths. */
2
+ export type PathMatcher = (path: string) => boolean;
3
+ /**
4
+ * Build a matcher for a list of gitignore-style patterns. Compile once per
5
+ * run and reuse it across every path in the diff.
6
+ */
7
+ export declare function createPathMatcher(patterns: string[]): PathMatcher;
8
+ /** Whether `path` matches any of the gitignore-style `patterns`. */
9
+ export declare function matchesPathPatterns(path: string, patterns: string[]): boolean;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Prompt construction for the two-stage pipeline.
3
+ *
4
+ * Stage 1 (summary model): read a diff chunk and describe the change factually.
5
+ * Stage 2 (final model): turn the summaries into a commit message that obeys the
6
+ * configured formatting rules (conventional commits, gitmoji, template, body).
7
+ */
8
+ import type { ChangePriority, Config, DiffSummary } from "./types.ts";
9
+ /** Sentinel separating candidate messages in interactive mode. */
10
+ export declare const OPTION_DELIMITER = "===OPTION===";
11
+ /**
12
+ * System prompt for the diff-summarization stage. The low-priority variant
13
+ * asks for a deliberately short summary: the final model only needs to know
14
+ * which areas changed and how, so the churn cannot crowd out the primary
15
+ * changes when the summaries are combined.
16
+ */
17
+ export declare function buildSummarySystem(priority?: ChangePriority): string;
18
+ /** User prompt for a single diff chunk in the summarization stage. */
19
+ export declare function buildSummaryUser(chunk: string, index: number, total: number, priority?: ChangePriority): string;
20
+ /**
21
+ * JSON schema for the final stage's structured output: a list of candidate
22
+ * commit messages. Requesting this makes parsing robust regardless of how the
23
+ * model chooses to format its prose.
24
+ */
25
+ export declare const MESSAGES_SCHEMA: Record<string, unknown>;
26
+ /** Pull the message list out of a structured-output object, or return null if malformed. */
27
+ export declare function extractMessages(structured: unknown): string[] | null;
28
+ /**
29
+ * System prompt for the final commit-message stage, encoding all formatting
30
+ * rules. When `structured` is true, the model returns its messages as JSON, so
31
+ * the "no markdown" guidance is scoped to each message's own text. When
32
+ * `hasLowPriority` is true the summaries come in two priority groups and the
33
+ * weighting rules ({@link lowPriorityWeightingRules}) are added between the
34
+ * subject-line rules and the body rule, in the order the constraints apply.
35
+ */
36
+ export declare function buildFinalSystem(config: Config, structured?: boolean, hasLowPriority?: boolean): string;
37
+ /** Whether the summaries span both priority groups (the only case that needs the weighting rules). */
38
+ export declare function hasLowPrioritySummaries(summaries: DiffSummary[]): boolean;
39
+ /**
40
+ * User prompt for the final stage.
41
+ *
42
+ * Summaries are presented by priority group (see {@link describeSummaries}).
43
+ * In `structured` mode the candidates are returned via {@link MESSAGES_SCHEMA}'s
44
+ * `messages` array. Otherwise, when `count` > 1, they are separated by
45
+ * {@link OPTION_DELIMITER} for text parsing.
46
+ */
47
+ export declare function buildFinalUser(summaries: DiffSummary[], count?: number, structured?: boolean): string;
48
+ /**
49
+ * Final-stage input for filenamesOnly. JSON-quoted paths keep embedded
50
+ * newlines and quotes inside a single list item. No diff content is included.
51
+ */
52
+ export declare function buildFilenamesUser(filenames: {
53
+ primary: string[];
54
+ lowPriority: string[];
55
+ }, count?: number, structured?: boolean): string;
56
+ /** Parse the multi-option response from the final stage into individual messages. */
57
+ export declare function parseOptions(text: string): string[];
58
+ /** Strip stray formatting a model may add despite instructions (fences, wrapping quotes). */
59
+ export declare function cleanMessage(text: string): string;