@andre-barbosa/opencode-commit 0.1.1 → 0.1.3

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,112 @@
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 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
+ - Dedicated model for commit message generation
11
+ - Conventional Commits format enforced (`feat(scope): ...`, `fix(scope): ...`, etc.)
12
+ - Optional hint: `/commit emphasize breaking API change`
13
+ - Uses staged diffs, unstaged diffs, non-ignored untracked filenames, 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 uncommitted changes
19
+
20
+ ## Installation
21
+
22
+ ### Configure the plugin
23
+
24
+ **Local development** (this repo):
25
+
26
+ ```json
27
+ {
28
+ "$schema": "https://opencode.ai/config.json",
29
+ "plugin": [
30
+ [
31
+ "file:///C:/absolute/path/to/opencode-commit/src/index.ts",
32
+ {
33
+ "model": "opencode-go/deepseek-v4-flash",
34
+ "maxDiffChars": 12000
35
+ }
36
+ ]
37
+ ]
38
+ }
39
+ ```
40
+
41
+ **From npm:**
42
+
43
+ ```json
44
+ {
45
+ "$schema": "https://opencode.ai/config.json",
46
+ "plugin": [
47
+ ["@andre-barbosa/opencode-commit", { "model": "opencode-go/deepseek-v4-flash" }]
48
+ ]
49
+ }
50
+ ```
51
+
52
+ See [opencode.example.json](opencode.example.json) for a full example.
53
+
54
+ Run `opencode models` to list available models.
55
+ Restart OpenCode after editing config.
56
+
57
+ ## Usage
58
+
59
+ 1. Make changes in a git repository. Staging is optional.
60
+ 2. Open OpenCode in the project.
61
+ 3. Run `/commit`.
62
+ 4. Copy the suggested message and commit: `git commit -m "feat(scope): ..."`.
63
+
64
+ Optional extra instruction:
65
+
66
+ ```text
67
+ /commit focus on test coverage improvements
68
+ ```
69
+
70
+ ## Plugin options
71
+
72
+ When loading the plugin as a tuple `[name, options]`:
73
+
74
+ | Option | Default | Description |
75
+ | --- | --- | --- |
76
+ | `model` | required | Model to use in `provider/model-id` format |
77
+ | `maxDiffChars` | `12000` | Max tracked diff characters sent across staged and unstaged diffs |
78
+
79
+ ## Change scope
80
+
81
+ `/commit` analyzes:
82
+
83
+ - Staged tracked changes from `git diff --staged`
84
+ - Unstaged tracked changes from `git diff`
85
+ - Non-ignored untracked filenames from `git ls-files --others --exclude-standard`
86
+
87
+ It does not read untracked file contents and does not ask Git for ignored files.
88
+
89
+ ## Commit message format
90
+
91
+ Generated messages follow [Conventional Commits](https://www.conventionalcommits.org/):
92
+
93
+ ```
94
+ type(scope): short imperative description
95
+
96
+ - optional bullet body
97
+ ```
98
+
99
+ Allowed types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`.
100
+
101
+ ## Project layout
102
+
103
+ ```
104
+ opencode-commit/
105
+ ├── src/ # plugin source
106
+ ├── commands/ # optional /commit command stub
107
+ └── opencode.example.json
108
+ ```
109
+
110
+ ## License
111
+
112
+ MIT
@@ -1,5 +1,5 @@
1
- ---
2
- description: Generate a commit message from staged changes
3
- ---
4
-
5
- Handled by the opencode-commit plugin.
1
+ ---
2
+ description: Generate a commit message from uncommitted changes
3
+ ---
4
+
5
+ Handled by the opencode-commit plugin.
@@ -1,17 +1,12 @@
1
- {
2
- "$schema": "https://opencode.ai/config.json",
3
- "plugin": [
4
- [
5
- "@andre-barbosa/opencode-commit",
6
- {
7
- "agent": "commit-writer",
8
- "maxDiffChars": 12000
9
- }
10
- ]
11
- ],
12
- "agent": {
13
- "commit-writer": {
14
- "model": "opencode-go/deepseek-v4-flash"
15
- }
16
- }
17
- }
1
+ {
2
+ "$schema": "https://opencode.ai/config.json",
3
+ "plugin": [
4
+ [
5
+ "@andre-barbosa/opencode-commit",
6
+ {
7
+ "model": "your-provider/your-model",
8
+ "maxDiffChars": 12000
9
+ }
10
+ ]
11
+ ]
12
+ }
package/package.json CHANGED
@@ -1,51 +1,50 @@
1
- {
2
- "name": "@andre-barbosa/opencode-commit",
3
- "version": "0.1.1",
4
- "description": "OpenCode plugin that generates Conventional Commit messages via a dedicated sub-agent",
5
- "type": "module",
6
- "main": "src/index.ts",
7
- "exports": {
8
- ".": "./src/index.ts"
9
- },
10
- "files": [
11
- "src",
12
- "agents",
13
- "commands",
14
- "opencode.example.json",
15
- "README.md",
16
- "LICENSE"
17
- ],
18
- "scripts": {
19
- "typecheck": "tsc --noEmit",
20
- "prepublishOnly": "npm run typecheck"
21
- },
22
- "keywords": [
23
- "opencode",
24
- "opencode-plugin",
25
- "git",
26
- "commit",
27
- "conventional-commits"
28
- ],
29
- "license": "MIT",
30
- "repository": {
31
- "type": "git",
32
- "url": "https://github.com/AndreB10/opencode-commit.git"
33
- },
34
- "bugs": {
35
- "url": "https://github.com/AndreB10/opencode-commit/issues"
36
- },
37
- "homepage": "https://github.com/AndreB10/opencode-commit#readme",
38
- "publishConfig": {
39
- "access": "public"
40
- },
41
- "dependencies": {
42
- "@opencode-ai/plugin": "^1.3.3"
43
- },
44
- "devDependencies": {
45
- "@opencode-ai/sdk": "^1.3.3",
46
- "typescript": "^5.8.3"
47
- },
48
- "engines": {
49
- "node": ">=18"
50
- }
51
- }
1
+ {
2
+ "name": "@andre-barbosa/opencode-commit",
3
+ "version": "0.1.3",
4
+ "description": "OpenCode plugin that generates Conventional Commit messages via /commit using a configured model",
5
+ "type": "module",
6
+ "main": "src/index.ts",
7
+ "exports": {
8
+ ".": "./src/index.ts"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "commands",
13
+ "opencode.example.json",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "scripts": {
18
+ "typecheck": "tsc --noEmit",
19
+ "prepublishOnly": "npm run typecheck"
20
+ },
21
+ "keywords": [
22
+ "opencode",
23
+ "opencode-plugin",
24
+ "git",
25
+ "commit",
26
+ "conventional-commits"
27
+ ],
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/AndreB10/opencode-commit.git"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/AndreB10/opencode-commit/issues"
35
+ },
36
+ "homepage": "https://github.com/AndreB10/opencode-commit#readme",
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "dependencies": {
41
+ "@opencode-ai/plugin": "^1.3.3"
42
+ },
43
+ "devDependencies": {
44
+ "@opencode-ai/sdk": "^1.3.3",
45
+ "typescript": "^5.8.3"
46
+ },
47
+ "engines": {
48
+ "node": ">=18"
49
+ }
50
+ }
package/src/generate.ts CHANGED
@@ -1,112 +1,89 @@
1
- import type { createOpencodeClient } from "@opencode-ai/sdk";
2
- import type { AgentInfo, ModelRef } from "./types.js";
3
- import { buildCommitPrompt, stripCodeFences } from "./prompt.js";
4
- import type { GitContext } from "./types.js";
5
-
6
- type Client = ReturnType<typeof createOpencodeClient>;
7
-
8
- function extractText(parts: Array<{ type?: string; text?: string }>): string {
9
- return parts
10
- .filter((part) => part.type === "text" && typeof part.text === "string")
11
- .map((part) => part.text as string)
12
- .join("")
13
- .trim();
14
- }
15
-
16
- export function formatModelRef(model?: ModelRef): string {
17
- if (!model) return "default";
18
- return `${model.providerID}/${model.modelID}`;
19
- }
20
-
21
- export async function fetchAgent(
22
- client: Client,
23
- agentName: string,
24
- ): Promise<AgentInfo | undefined> {
25
- const { data, error } = await client.app.agents();
26
- if (error || !Array.isArray(data)) return undefined;
27
-
28
- const agent = data.find((entry) => entry.name === agentName);
29
- if (!agent) return undefined;
30
-
31
- return {
32
- name: agent.name,
33
- model: agent.model
34
- ? {
35
- providerID: agent.model.providerID,
36
- modelID: agent.model.modelID,
37
- }
38
- : undefined,
39
- };
40
- }
41
-
42
- async function createChildSession(
43
- client: Client,
44
- parentSessionID: string,
45
- ): Promise<string> {
46
- const { data: session, error } = await client.session.create({
47
- body: {
48
- title: "Commit message",
49
- parentID: parentSessionID,
50
- },
51
- });
52
-
53
- if (error || !session?.id) {
54
- throw new Error(`Failed to create child session: ${String(error)}`);
55
- }
56
-
57
- return session.id;
58
- }
59
-
60
- export async function generateCommitMessage(input: {
61
- client: Client;
62
- parentSessionID: string;
63
- agentName: string;
64
- agent: AgentInfo;
65
- context: GitContext;
66
- userHint?: string;
67
- }): Promise<{ message: string; childSessionID: string; modelLabel: string }> {
68
- const childSessionID = await createChildSession(
69
- input.client,
70
- input.parentSessionID,
71
- );
72
-
73
- const prompt = buildCommitPrompt(input.context, input.userHint);
74
- const body: {
75
- agent: string;
76
- parts: Array<{ type: "text"; text: string }>;
77
- model?: ModelRef;
78
- } = {
79
- agent: input.agentName,
80
- parts: [{ type: "text", text: prompt }],
81
- };
82
-
83
- if (input.agent.model) {
84
- body.model = input.agent.model;
85
- }
86
-
87
- const { data: result, error } = await input.client.session.prompt({
88
- path: { id: childSessionID },
89
- body,
90
- });
91
-
92
- if (error) {
93
- throw new Error(`Sub-agent prompt failed: ${String(error)}`);
94
- }
95
-
96
- const parts =
97
- (result as { parts?: Array<{ type?: string; text?: string }> } | undefined)
98
- ?.parts ?? [];
99
- const rawText = extractText(parts);
100
-
101
- if (!rawText) {
102
- throw new Error(
103
- `Sub-agent returned no text. Child session: ${childSessionID}`,
104
- );
105
- }
106
-
107
- return {
108
- message: stripCodeFences(rawText),
109
- childSessionID,
110
- modelLabel: formatModelRef(input.agent.model),
111
- };
112
- }
1
+ import type { createOpencodeClient } from "@opencode-ai/sdk";
2
+ import type { ModelRef } from "./types.js";
3
+ import {
4
+ buildCommitPrompt,
5
+ COMMIT_SYSTEM_PROMPT,
6
+ stripCodeFences,
7
+ } from "./prompt.js";
8
+ import type { GitContext } from "./types.js";
9
+
10
+ type Client = ReturnType<typeof createOpencodeClient>;
11
+ const COMMIT_AGENT = "plan";
12
+
13
+ function extractText(parts: Array<{ type?: string; text?: string }>): string {
14
+ return parts
15
+ .filter((part) => part.type === "text" && typeof part.text === "string")
16
+ .map((part) => part.text as string)
17
+ .join("")
18
+ .trim();
19
+ }
20
+
21
+ export function formatModelRef(model?: ModelRef): string {
22
+ if (!model) return "default";
23
+ return `${model.providerID}/${model.modelID}`;
24
+ }
25
+
26
+ async function createChildSession(
27
+ client: Client,
28
+ parentSessionID: string,
29
+ ): Promise<string> {
30
+ const { data: session, error } = await client.session.create({
31
+ body: {
32
+ title: "Commit message",
33
+ parentID: parentSessionID,
34
+ },
35
+ });
36
+
37
+ if (error || !session?.id) {
38
+ throw new Error(`Failed to create child session: ${String(error)}`);
39
+ }
40
+
41
+ return session.id;
42
+ }
43
+
44
+ export async function generateCommitMessage(input: {
45
+ client: Client;
46
+ parentSessionID: string;
47
+ model: ModelRef;
48
+ context: GitContext;
49
+ userHint?: string;
50
+ }): Promise<{ message: string; childSessionID: string; modelLabel: string }> {
51
+ const childSessionID = await createChildSession(
52
+ input.client,
53
+ input.parentSessionID,
54
+ );
55
+
56
+ const prompt = buildCommitPrompt(input.context, input.userHint);
57
+ const body = {
58
+ agent: COMMIT_AGENT,
59
+ model: input.model,
60
+ system: COMMIT_SYSTEM_PROMPT,
61
+ parts: [{ type: "text" as const, text: prompt }],
62
+ };
63
+
64
+ const { data: result, error } = await input.client.session.prompt({
65
+ path: { id: childSessionID },
66
+ body,
67
+ });
68
+
69
+ if (error) {
70
+ throw new Error(`Commit prompt failed: ${String(error)}`);
71
+ }
72
+
73
+ const parts =
74
+ (result as { parts?: Array<{ type?: string; text?: string }> } | undefined)
75
+ ?.parts ?? [];
76
+ const rawText = extractText(parts);
77
+
78
+ if (!rawText) {
79
+ throw new Error(
80
+ `Commit model returned no text. Child session: ${childSessionID}`,
81
+ );
82
+ }
83
+
84
+ return {
85
+ message: stripCodeFences(rawText),
86
+ childSessionID,
87
+ modelLabel: formatModelRef(input.model),
88
+ };
89
+ }