@andre-barbosa/opencode-commit 0.1.1 → 0.1.4

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.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 André Barbosa
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 André Barbosa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,127 +1,137 @@
1
- # @andre-barbosa/opencode-commit
2
-
3
- OpenCode plugin that generates **Conventional Commits** messages from staged git changes using a dedicated sub-agent on a model you choose.
4
-
5
- Run `/commit` in the OpenCode TUI to get a suggested message like `feat(auth): add oauth login flow` — copy it and commit manually.
6
-
7
- ## Features
8
-
9
- - `/commit` slash command (display only, no auto-commit)
10
- - Dedicated hidden `commit-writer` sub-agent with its own model
11
- - Conventional Commits format enforced (`feat(scope): ...`, `fix(scope): ...`, etc.)
12
- - Optional hint: `/commit emphasize breaking API change`
13
- - Uses staged diff, branch name, and recent commit subjects for context
14
-
15
- ## Requirements
16
-
17
- - [OpenCode](https://opencode.ai/) with plugin support
18
- - A git repository with staged changes
19
-
20
- ## Installation
21
-
22
- ### 1. Copy agent and command templates
23
-
24
- ```bash
25
- mkdir -p .opencode/agents .opencode/commands
26
- cp agents/commit-writer.md .opencode/agents/commit-writer.md
27
- cp commands/commit.md .opencode/commands/commit.md
28
- ```
29
-
30
- ### 2. Configure the plugin
31
-
32
- **Local development** (this repo):
33
-
34
- ```json
35
- {
36
- "$schema": "https://opencode.ai/config.json",
37
- "plugin": [
38
- [
39
- "file:///C:/absolute/path/to/opencode-commit/src/index.ts",
40
- {
41
- "agent": "commit-writer",
42
- "maxDiffChars": 12000
43
- }
44
- ]
45
- ],
46
- "agent": {
47
- "commit-writer": {
48
- "model": "opencode-go/deepseek-v4-flash"
49
- }
50
- }
51
- }
52
- ```
53
-
54
- **From npm:**
55
-
56
- ```json
57
- {
58
- "plugin": [
59
- ["@andre-barbosa/opencode-commit", { "agent": "commit-writer" }]
60
- ],
61
- "agent": {
62
- "commit-writer": {
63
- "model": "opencode-go/deepseek-v4-flash"
64
- }
65
- }
66
- }
67
- ```
68
-
69
- See [opencode.example.json](opencode.example.json) for a full example.
70
-
71
- ### 3. Set your model
72
-
73
- Edit the model on the `commit-writer` agent in `opencode.json` or in `.opencode/agents/commit-writer.md`:
74
-
75
- ```yaml
76
- model: opencode-go/deepseek-v4-flash
77
- ```
78
-
79
- Run `opencode models` to list available models.
80
-
81
- ## Usage
82
-
83
- 1. Stage your changes: `git add ...`
84
- 2. Open OpenCode in the project
85
- 3. Run `/commit`
86
- 4. Copy the suggested message and commit: `git commit -m "feat(scope): ..."`
87
-
88
- Optional extra instruction:
89
-
90
- ```text
91
- /commit focus on test coverage improvements
92
- ```
93
-
94
- ## Plugin options
95
-
96
- When loading the plugin as a tuple `[name, options]`:
97
-
98
- | Option | Default | Description |
99
- | --- | --- | --- |
100
- | `agent` | `commit-writer` | Sub-agent name to invoke |
101
- | `maxDiffChars` | `12000` | Max staged diff characters sent to the sub-agent |
102
-
103
- ## Commit message format
104
-
105
- Generated messages follow [Conventional Commits](https://www.conventionalcommits.org/):
106
-
107
- ```
108
- type(scope): short imperative description
109
-
110
- - optional bullet body
111
- ```
112
-
113
- Allowed types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`.
114
-
115
- ## Project layout
116
-
117
- ```
118
- opencode-commit/
119
- ├── src/ # plugin source
120
- ├── agents/ # commit-writer sub-agent template
121
- ├── commands/ # /commit command stub
122
- └── opencode.example.json
123
- ```
124
-
125
- ## License
126
-
127
- MIT
1
+ # @andre-barbosa/opencode-commit
2
+
3
+ OpenCode plugin that generates **Conventional Commits** messages or split commit plans from uncommitted git changes using a model you choose.
4
+
5
+ Run `/commit` in the OpenCode TUI to get a suggested message like `feat(auth): add oauth login flow` — copy it and commit manually.
6
+
7
+ ## Features
8
+
9
+ - `/commit` slash command (display only, no auto-commit)
10
+ - Split commit planning with `/commit split` and `/commit split <folder...>`
11
+ - Dedicated model for commit message generation
12
+ - Conventional Commits format enforced (`feat(scope): ...`, `fix(scope): ...`, etc.)
13
+ - Optional hint: `/commit emphasize breaking API change`
14
+ - Uses staged diffs, unstaged diffs, non-ignored untracked filenames, branch name, and recent commit subjects for context
15
+
16
+ ## Requirements
17
+
18
+ - [OpenCode](https://opencode.ai/) with plugin support
19
+ - A git repository with uncommitted changes
20
+
21
+ ## Installation
22
+
23
+ ### Configure the plugin
24
+
25
+ **Local development** (this repo):
26
+
27
+ Build the plugin first:
28
+
29
+ ```bash
30
+ npm run build
31
+ ```
32
+
33
+ ```json
34
+ {
35
+ "$schema": "https://opencode.ai/config.json",
36
+ "plugin": [
37
+ [
38
+ "file:///C:/absolute/path/to/opencode-commit/dist/index.js",
39
+ {
40
+ "model": "opencode-go/deepseek-v4-flash",
41
+ "maxDiffChars": 12000
42
+ }
43
+ ]
44
+ ]
45
+ }
46
+ ```
47
+
48
+ **From npm:**
49
+
50
+ ```json
51
+ {
52
+ "$schema": "https://opencode.ai/config.json",
53
+ "plugin": [
54
+ ["@andre-barbosa/opencode-commit", { "model": "opencode-go/deepseek-v4-flash" }]
55
+ ]
56
+ }
57
+ ```
58
+
59
+ See [opencode.example.json](opencode.example.json) for a full example.
60
+
61
+ Run `opencode models` to list available models.
62
+ Restart OpenCode after editing config.
63
+ When testing local changes, run `npm run build` again before restarting OpenCode.
64
+
65
+ ## Usage
66
+
67
+ 1. Make changes in a git repository. Staging is optional.
68
+ 2. Open OpenCode in the project.
69
+ 3. Run `/commit`.
70
+ 4. Copy the suggested message and commit: `git commit -m "feat(scope): ..."`.
71
+
72
+ Optional extra instruction:
73
+
74
+ ```text
75
+ /commit focus on test coverage improvements
76
+ ```
77
+
78
+ Split commit planning:
79
+
80
+ ```text
81
+ /commit split
82
+ ```
83
+
84
+ This suggests one commit message per changed top-level folder. Files in the repo root are grouped under `root`.
85
+
86
+ ```text
87
+ /commit split apps/web packages/api
88
+ ```
89
+
90
+ This suggests one commit message for each requested folder with changes under that path.
91
+
92
+ Split mode only suggests a plan. It does not stage files and does not create commits.
93
+
94
+ ## Plugin options
95
+
96
+ When loading the plugin as a tuple `[name, options]`:
97
+
98
+ | Option | Default | Description |
99
+ | --- | --- | --- |
100
+ | `model` | required | Model to use in `provider/model-id` format |
101
+ | `maxDiffChars` | `12000` | Max tracked diff characters sent across staged and unstaged diffs |
102
+
103
+ ## Change scope
104
+
105
+ `/commit` analyzes:
106
+
107
+ - Staged tracked changes from `git diff --staged`
108
+ - Unstaged tracked changes from `git diff`
109
+ - Changed-file metadata from `git diff --name-status`
110
+ - Non-ignored untracked filenames from `git ls-files --others --exclude-standard`
111
+
112
+ It does not read untracked file contents and does not ask Git for ignored files.
113
+
114
+ ## Commit message format
115
+
116
+ Generated messages follow [Conventional Commits](https://www.conventionalcommits.org/):
117
+
118
+ ```
119
+ type(scope): short imperative description
120
+
121
+ - optional bullet body
122
+ ```
123
+
124
+ Allowed types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`.
125
+
126
+ ## Project layout
127
+
128
+ ```
129
+ opencode-commit/
130
+ ├── src/ # plugin source
131
+ ├── commands/ # optional /commit command stub
132
+ └── opencode.example.json
133
+ ```
134
+
135
+ ## License
136
+
137
+ MIT
@@ -0,0 +1,18 @@
1
+ import type { createOpencodeClient } from "@opencode-ai/sdk";
2
+ import type { CommitGroup, CommitRequest, ModelRef } from "./types.js";
3
+ import type { GitContext } from "./types.js";
4
+ type Client = ReturnType<typeof createOpencodeClient>;
5
+ export declare function formatModelRef(model?: ModelRef): string;
6
+ export declare function generateCommitMessage(input: {
7
+ client: Client;
8
+ parentSessionID: string;
9
+ model: ModelRef;
10
+ context: GitContext;
11
+ request: CommitRequest;
12
+ groups?: CommitGroup[];
13
+ }): Promise<{
14
+ content: string;
15
+ childSessionID: string;
16
+ modelLabel: string;
17
+ }>;
18
+ export {};
@@ -0,0 +1,58 @@
1
+ import { buildCommitPrompt, COMMIT_SYSTEM_PROMPT, stripCodeFences, } from "./prompt.js";
2
+ const COMMIT_AGENT = "plan";
3
+ function extractText(parts) {
4
+ return parts
5
+ .filter((part) => part.type === "text" && typeof part.text === "string")
6
+ .map((part) => part.text)
7
+ .join("")
8
+ .trim();
9
+ }
10
+ export function formatModelRef(model) {
11
+ if (!model)
12
+ return "default";
13
+ return `${model.providerID}/${model.modelID}`;
14
+ }
15
+ async function createChildSession(client, parentSessionID) {
16
+ const { data: session, error } = await client.session.create({
17
+ body: {
18
+ title: "Commit message",
19
+ parentID: parentSessionID,
20
+ },
21
+ });
22
+ if (error || !session?.id) {
23
+ throw new Error(`Failed to create child session: ${String(error)}`);
24
+ }
25
+ return session.id;
26
+ }
27
+ export async function generateCommitMessage(input) {
28
+ const childSessionID = await createChildSession(input.client, input.parentSessionID);
29
+ const prompt = buildCommitPrompt({
30
+ context: input.context,
31
+ request: input.request,
32
+ groups: input.groups,
33
+ });
34
+ const body = {
35
+ agent: COMMIT_AGENT,
36
+ model: input.model,
37
+ system: COMMIT_SYSTEM_PROMPT,
38
+ parts: [{ type: "text", text: prompt }],
39
+ };
40
+ const { data: result, error } = await input.client.session.prompt({
41
+ path: { id: childSessionID },
42
+ body,
43
+ });
44
+ if (error) {
45
+ throw new Error(`Commit prompt failed: ${String(error)}`);
46
+ }
47
+ const parts = result
48
+ ?.parts ?? [];
49
+ const rawText = extractText(parts);
50
+ if (!rawText) {
51
+ throw new Error(`Commit model returned no text. Child session: ${childSessionID}`);
52
+ }
53
+ return {
54
+ content: stripCodeFences(rawText),
55
+ childSessionID,
56
+ modelLabel: formatModelRef(input.model),
57
+ };
58
+ }
package/dist/git.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import type { PluginInput } from "@opencode-ai/plugin";
2
+ import type { GitContext } from "./types.js";
3
+ type Shell = PluginInput["$"];
4
+ export declare function isGitRepo($: Shell, worktree: string): Promise<boolean>;
5
+ export declare function gatherGitContext($: Shell, worktree: string, maxDiffChars: number): Promise<GitContext>;
6
+ export {};
package/dist/git.js ADDED
@@ -0,0 +1,98 @@
1
+ async function readGitOutput($, worktree, run) {
2
+ const result = await run($);
3
+ if (result.exitCode !== 0) {
4
+ return "";
5
+ }
6
+ return result.stdout?.toString().trim() ?? "";
7
+ }
8
+ export async function isGitRepo($, worktree) {
9
+ const result = await $ `git rev-parse --git-dir`.cwd(worktree).quiet().nothrow();
10
+ return result.exitCode === 0;
11
+ }
12
+ function truncateDiff(diff, maxChars, label) {
13
+ if (diff.length <= maxChars)
14
+ return diff;
15
+ return `${diff.slice(0, maxChars)}\n\n[${label} diff truncated at ${maxChars} characters]`;
16
+ }
17
+ function splitDiffs(input) {
18
+ if (!input.staged || !input.unstaged) {
19
+ return {
20
+ staged: truncateDiff(input.staged, input.maxDiffChars, "staged"),
21
+ unstaged: truncateDiff(input.unstaged, input.maxDiffChars, "unstaged"),
22
+ };
23
+ }
24
+ const half = Math.floor(input.maxDiffChars / 2);
25
+ let stagedLimit = Math.min(input.staged.length, half);
26
+ let unstagedLimit = Math.min(input.unstaged.length, input.maxDiffChars - stagedLimit);
27
+ stagedLimit = Math.min(input.staged.length, input.maxDiffChars - unstagedLimit);
28
+ return {
29
+ staged: truncateDiff(input.staged, stagedLimit, "staged"),
30
+ unstaged: truncateDiff(input.unstaged, unstagedLimit, "unstaged"),
31
+ };
32
+ }
33
+ function parseNameStatus(output, source) {
34
+ if (!output)
35
+ return [];
36
+ return output
37
+ .split("\n")
38
+ .map((line) => line.trim())
39
+ .filter(Boolean)
40
+ .map((line) => {
41
+ const [status, ...paths] = line.split("\t");
42
+ return {
43
+ path: paths[paths.length - 1] ?? "",
44
+ status: status ?? "?",
45
+ source,
46
+ };
47
+ })
48
+ .filter((file) => file.path.length > 0);
49
+ }
50
+ export async function gatherGitContext($, worktree, maxDiffChars) {
51
+ const branch = (await readGitOutput($, worktree, ($) => $ `git symbolic-ref --quiet --short HEAD`.cwd(worktree).quiet().nothrow())) ||
52
+ (await readGitOutput($, worktree, ($) => $ `git rev-parse --short HEAD`.cwd(worktree).quiet().nothrow())) ||
53
+ "unknown";
54
+ const stagedStat = await readGitOutput($, worktree, ($) => $ `git diff --staged --stat`.cwd(worktree).quiet().nothrow());
55
+ const rawStagedDiff = await readGitOutput($, worktree, ($) => $ `git diff --staged`.cwd(worktree).quiet().nothrow());
56
+ const stagedNameStatus = await readGitOutput($, worktree, ($) => $ `git diff --staged --name-status`.cwd(worktree).quiet().nothrow());
57
+ const unstagedStat = await readGitOutput($, worktree, ($) => $ `git diff --stat`.cwd(worktree).quiet().nothrow());
58
+ const rawUnstagedDiff = await readGitOutput($, worktree, ($) => $ `git diff`.cwd(worktree).quiet().nothrow());
59
+ const unstagedNameStatus = await readGitOutput($, worktree, ($) => $ `git diff --name-status`.cwd(worktree).quiet().nothrow());
60
+ const untrackedOutput = await readGitOutput($, worktree, ($) => $ `git ls-files --others --exclude-standard`.cwd(worktree).quiet().nothrow());
61
+ const untrackedFiles = untrackedOutput
62
+ ? untrackedOutput.split("\n").map((line) => line.trim()).filter(Boolean)
63
+ : [];
64
+ const diffs = splitDiffs({
65
+ staged: rawStagedDiff,
66
+ unstaged: rawUnstagedDiff,
67
+ maxDiffChars,
68
+ });
69
+ const changedFiles = [
70
+ ...parseNameStatus(stagedNameStatus, "staged"),
71
+ ...parseNameStatus(unstagedNameStatus, "unstaged"),
72
+ ...untrackedFiles.map((path) => ({
73
+ path,
74
+ status: "A",
75
+ source: "untracked",
76
+ })),
77
+ ];
78
+ const logOutput = await readGitOutput($, worktree, ($) => $ `git log -5 --pretty=format:%s`.cwd(worktree).quiet().nothrow());
79
+ const recentCommits = logOutput
80
+ ? logOutput.split("\n").map((line) => line.trim()).filter(Boolean)
81
+ : [];
82
+ const hasUncommittedChanges = stagedStat.length > 0 ||
83
+ rawStagedDiff.length > 0 ||
84
+ unstagedStat.length > 0 ||
85
+ rawUnstagedDiff.length > 0 ||
86
+ untrackedFiles.length > 0;
87
+ return {
88
+ branch,
89
+ stagedStat,
90
+ stagedDiff: diffs.staged,
91
+ unstagedStat,
92
+ unstagedDiff: diffs.unstaged,
93
+ changedFiles,
94
+ untrackedFiles,
95
+ recentCommits,
96
+ hasUncommittedChanges,
97
+ };
98
+ }
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ export declare const OpenCodeCommitPlugin: Plugin;
3
+ export default OpenCodeCommitPlugin;
package/dist/index.js ADDED
@@ -0,0 +1,155 @@
1
+ import { gatherGitContext, isGitRepo } from "./git.js";
2
+ import { generateCommitMessage } from "./generate.js";
3
+ import { buildCommitGroups, parseCommitRequest, parseModelRef, resolveConfig, } from "./types.js";
4
+ const COMMAND_NAME = "commit";
5
+ const COMMAND_DESCRIPTION = "Generate a commit message or split commit plan from uncommitted changes";
6
+ const COMMAND_TEMPLATE = "Handled by the opencode-commit plugin.";
7
+ const SKIP_ERROR = "skip";
8
+ function textPart(text) {
9
+ return { type: "text", text };
10
+ }
11
+ function formatOutput(input) {
12
+ if (input.request.mode === "split") {
13
+ const missingFolders = input.missingFolders ?? [];
14
+ const missingBlock = missingFolders.length > 0
15
+ ? [
16
+ `No changes found for requested folder(s): ${formatFolderList(missingFolders)}.`,
17
+ "",
18
+ ]
19
+ : [];
20
+ return [
21
+ "## Suggested commit plan",
22
+ "",
23
+ ...missingBlock,
24
+ input.content,
25
+ "",
26
+ `Model: \`${input.modelLabel}\` · Child session: \`${input.childSessionID}\``,
27
+ "",
28
+ "Stage the listed files for each commit, then run `git commit` with the suggested message.",
29
+ ].join("\n");
30
+ }
31
+ return [
32
+ "## Suggested commit message",
33
+ "",
34
+ "```",
35
+ input.content,
36
+ "```",
37
+ "",
38
+ `Model: \`${input.modelLabel}\` · Child session: \`${input.childSessionID}\``,
39
+ "",
40
+ "Copy the message above and run `git commit` when ready.",
41
+ ].join("\n");
42
+ }
43
+ function formatFolderList(folders) {
44
+ return folders.map((folder) => `\`${folder}\``).join(", ");
45
+ }
46
+ const skipCommand = () => {
47
+ throw new Error(SKIP_ERROR);
48
+ };
49
+ export const OpenCodeCommitPlugin = async ({ client, $, worktree }, options) => {
50
+ const config = resolveConfig(options);
51
+ const log = async (level, message, extra) => {
52
+ await client.app
53
+ .log({
54
+ body: {
55
+ service: "opencode-commit",
56
+ level,
57
+ message,
58
+ extra,
59
+ },
60
+ })
61
+ .catch(() => { });
62
+ };
63
+ return {
64
+ config: async (cfg) => {
65
+ cfg.command ??= {};
66
+ cfg.command[COMMAND_NAME] = {
67
+ description: COMMAND_DESCRIPTION,
68
+ template: COMMAND_TEMPLATE,
69
+ subtask: true,
70
+ };
71
+ if (config.model) {
72
+ cfg.command[COMMAND_NAME].model = config.model;
73
+ }
74
+ },
75
+ "command.execute.before": async (input, output) => {
76
+ if (input.command !== COMMAND_NAME)
77
+ return;
78
+ try {
79
+ if (!config.model) {
80
+ output.parts = [
81
+ textPart([
82
+ "No commit model configured.",
83
+ "",
84
+ "Add a `model` option to the opencode-commit plugin config, for example:",
85
+ "`[\"@andre-barbosa/opencode-commit\", { \"model\": \"opencode-go/deepseek-v4-flash\" }]`",
86
+ ].join("\n")),
87
+ ];
88
+ return skipCommand();
89
+ }
90
+ const model = parseModelRef(config.model);
91
+ const request = parseCommitRequest(input.arguments);
92
+ if (!(await isGitRepo($, worktree))) {
93
+ output.parts = [
94
+ textPart("Not a git repository. Run `/commit` from inside a git worktree."),
95
+ ];
96
+ return skipCommand();
97
+ }
98
+ const context = await gatherGitContext($, worktree, config.maxDiffChars);
99
+ if (!context.hasUncommittedChanges) {
100
+ output.parts = [
101
+ textPart("No uncommitted changes found. Make changes and run `/commit` again."),
102
+ ];
103
+ return skipCommand();
104
+ }
105
+ let groups;
106
+ let missingFolders = [];
107
+ if (request.mode === "split") {
108
+ const grouped = buildCommitGroups(context.changedFiles, request.folders);
109
+ groups = grouped.groups;
110
+ missingFolders = grouped.missingFolders;
111
+ if (groups.length === 0) {
112
+ const target = request.folders.length > 0
113
+ ? `requested folder(s): ${formatFolderList(request.folders)}`
114
+ : "changed folders";
115
+ output.parts = [textPart(`No changes found for ${target}.`)];
116
+ return skipCommand();
117
+ }
118
+ }
119
+ await log("info", "Generating commit message", {
120
+ model: config.model,
121
+ mode: request.mode,
122
+ folders: request.mode === "split" ? request.folders : undefined,
123
+ branch: context.branch,
124
+ sessionID: input.sessionID,
125
+ });
126
+ const result = await generateCommitMessage({
127
+ client,
128
+ parentSessionID: input.sessionID,
129
+ model,
130
+ context,
131
+ request,
132
+ groups,
133
+ });
134
+ output.parts = [
135
+ textPart(formatOutput({ ...result, request, missingFolders })),
136
+ ];
137
+ return skipCommand();
138
+ }
139
+ catch (error) {
140
+ if (error instanceof Error && error.message === SKIP_ERROR) {
141
+ throw error;
142
+ }
143
+ await log("error", "Commit message generation failed", {
144
+ error: String(error),
145
+ sessionID: input.sessionID,
146
+ });
147
+ output.parts = [
148
+ textPart(`Failed to generate commit message: ${String(error)}`),
149
+ ];
150
+ return skipCommand();
151
+ }
152
+ },
153
+ };
154
+ };
155
+ export default OpenCodeCommitPlugin;
@@ -0,0 +1,8 @@
1
+ import type { CommitGroup, CommitRequest, GitContext } from "./types.js";
2
+ export declare const COMMIT_SYSTEM_PROMPT: string;
3
+ export declare function buildCommitPrompt(input: {
4
+ context: GitContext;
5
+ request: CommitRequest;
6
+ groups?: CommitGroup[];
7
+ }): string;
8
+ export declare function stripCodeFences(text: string): string;