opencode-commits 0.0.1-next.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.
- package/README.md +140 -0
- package/dist/commits.d.ts +4 -0
- package/dist/commits.d.ts.map +1 -0
- package/dist/commits.js +16 -0
- package/dist/commits.js.map +1 -0
- package/dist/src/config.d.ts +10 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +47 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/errors.d.ts +9 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +15 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/parser.d.ts +8 -0
- package/dist/src/parser.d.ts.map +1 -0
- package/dist/src/parser.js +84 -0
- package/dist/src/parser.js.map +1 -0
- package/dist/src/shell.d.ts +3 -0
- package/dist/src/shell.d.ts.map +1 -0
- package/dist/src/shell.js +2 -0
- package/dist/src/shell.js.map +1 -0
- package/dist/src/tools.d.ts +41 -0
- package/dist/src/tools.d.ts.map +1 -0
- package/dist/src/tools.js +103 -0
- package/dist/src/tools.js.map +1 -0
- package/dist/src/utils/safe.d.ts +14 -0
- package/dist/src/utils/safe.d.ts.map +1 -0
- package/dist/src/utils/safe.js +31 -0
- package/dist/src/utils/safe.js.map +1 -0
- package/dist/src/validator.d.ts +3 -0
- package/dist/src/validator.d.ts.map +1 -0
- package/dist/src/validator.js +36 -0
- package/dist/src/validator.js.map +1 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# opencode-commits
|
|
2
|
+
|
|
3
|
+
An [OpenCode](https://opencode.ai) plugin that enforces [Conventional Commits](https://www.conventionalcommits.org/) by validating commit messages before they reach git. Provides tools for committing, amending, diffing, and viewing git log -- all with built-in message parsing and validation.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
Add `opencode-commits` to your `opencode.json` plugin list:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"plugin": [
|
|
12
|
+
"opencode-commits"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Tools
|
|
18
|
+
|
|
19
|
+
### git-commit
|
|
20
|
+
|
|
21
|
+
Validate and commit staged changes with a conventional commit message.
|
|
22
|
+
|
|
23
|
+
| Parameter | Type | Required | Description |
|
|
24
|
+
| --------- | ------ | -------- | --------------------------------- |
|
|
25
|
+
| message | string | Yes | Conventional commit message |
|
|
26
|
+
|
|
27
|
+
Returns the git commit output on success. Rejects with validation errors and suggestions if the message is malformed.
|
|
28
|
+
|
|
29
|
+
### git-amend
|
|
30
|
+
|
|
31
|
+
Amend the last commit with a new validated conventional commit message.
|
|
32
|
+
|
|
33
|
+
| Parameter | Type | Required | Description |
|
|
34
|
+
| --------- | ------ | -------- | --------------------------------- |
|
|
35
|
+
| message | string | Yes | Conventional commit message |
|
|
36
|
+
|
|
37
|
+
### git-diff
|
|
38
|
+
|
|
39
|
+
Show the currently staged diff.
|
|
40
|
+
|
|
41
|
+
No parameters. Returns the output of `git diff --staged`, or a notice if nothing is staged.
|
|
42
|
+
|
|
43
|
+
### git-status
|
|
44
|
+
|
|
45
|
+
Show the working tree status including staged, unstaged, and untracked files.
|
|
46
|
+
|
|
47
|
+
No parameters. Returns the output of `git status`.
|
|
48
|
+
|
|
49
|
+
### git-log
|
|
50
|
+
|
|
51
|
+
List recent commits.
|
|
52
|
+
|
|
53
|
+
| Parameter | Type | Required | Description |
|
|
54
|
+
| --------- | ------ | -------- | ------------------------------------ |
|
|
55
|
+
| count | number | No | Number of commits to show (default: 10) |
|
|
56
|
+
|
|
57
|
+
Returns the output of `git log --oneline -n <count>`.
|
|
58
|
+
|
|
59
|
+
## Conventional Commits
|
|
60
|
+
|
|
61
|
+
All commit and amend operations validate messages against the [Conventional Commits](https://www.conventionalcommits.org/) specification before executing.
|
|
62
|
+
|
|
63
|
+
### Format
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
<type>[(<scope>)]: <description>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Supported Types
|
|
70
|
+
|
|
71
|
+
`feat` `fix` `build` `chore` `ci` `docs` `style` `refactor` `perf` `test` `revert`
|
|
72
|
+
|
|
73
|
+
### Scopes
|
|
74
|
+
|
|
75
|
+
Scopes are optional and configurable. When provided, they must:
|
|
76
|
+
|
|
77
|
+
- Start with a lowercase letter
|
|
78
|
+
- Contain only letters, numbers, and hyphens
|
|
79
|
+
- Be enclosed in parentheses after the type
|
|
80
|
+
|
|
81
|
+
### Validation Rules
|
|
82
|
+
|
|
83
|
+
- **Type** must be one of the supported types (lowercase only)
|
|
84
|
+
- **Scope** (if present) must follow the naming rules above
|
|
85
|
+
- **Description** must start with a lowercase letter
|
|
86
|
+
- **Description** must not end with punctuation (`.` `!` `,` `;` `:`)
|
|
87
|
+
- **Total message length** must not exceed 72 characters
|
|
88
|
+
|
|
89
|
+
Invalid messages are rejected with specific error details and suggestions for how to fix them.
|
|
90
|
+
|
|
91
|
+
### Unsupported Features
|
|
92
|
+
|
|
93
|
+
- **Breaking change indicator (`!`)** is not currently supported. Messages like `feat!: description` or `feat(scope)!: description` will be rejected by the parser.
|
|
94
|
+
|
|
95
|
+
To ensure all git commit operations go through the plugin's validation, disable direct `git commit` access in your `opencode.json` permissions. This forces the agent to use the plugin's tools instead of calling git directly, so every commit is validated against your conventions.
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"plugin": [
|
|
100
|
+
"opencode-commits"
|
|
101
|
+
],
|
|
102
|
+
"permission": {
|
|
103
|
+
"bash": {
|
|
104
|
+
"git commit *": "deny",
|
|
105
|
+
"git commit": "deny"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
This blocks the agent from running `git commit` directly through the shell, forcing it to use the plugin's validated tools instead. Other git commands like `git status` and `git add` remain unaffected.
|
|
112
|
+
|
|
113
|
+
## Configuration
|
|
114
|
+
|
|
115
|
+
Create an `opencode-commits.json` file in your project root to customize behavior:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"$schema": "https://raw.githubusercontent.com/whaaaley/opencode-commits/main/opencode-commits.schema.json",
|
|
120
|
+
"types": ["feat", "fix", "build", "chore", "ci", "docs", "style", "refactor", "perf", "test", "revert"],
|
|
121
|
+
"scopes": {
|
|
122
|
+
"app": ["portal", "dashboard", "settings"],
|
|
123
|
+
"layer": ["client", "server", "api", "database"],
|
|
124
|
+
"infra": ["ci", "build", "deploy", "docker"]
|
|
125
|
+
},
|
|
126
|
+
"maxLength": 72
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
| Field | Type | Description |
|
|
131
|
+
| --------- | ----------------- | ------------------------------------------------ |
|
|
132
|
+
| types | string[] | Allowed commit types (overrides defaults) |
|
|
133
|
+
| scopes | Record<string, string[]> | Named groups of valid scopes |
|
|
134
|
+
| maxLength | number | Max commit message length (default: 72) |
|
|
135
|
+
|
|
136
|
+
When scopes are configured, only the listed scopes are accepted. When omitted, any well-formed scope is allowed.
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commits.d.ts","sourceRoot":"","sources":["../commits.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAIjD,QAAA,MAAM,MAAM,EAAE,MAYb,CAAA;AAED,eAAe,MAAM,CAAA"}
|
package/dist/commits.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { loadConfig } from "./src/config.js";
|
|
2
|
+
import { createAmendTool, createCommitTool, createDiffTool, createLogTool, createStatusTool } from "./src/tools.js";
|
|
3
|
+
const plugin = async ({ directory, $ }) => {
|
|
4
|
+
const config = await loadConfig(directory);
|
|
5
|
+
return {
|
|
6
|
+
tool: {
|
|
7
|
+
'git-commit': createCommitTool($, config),
|
|
8
|
+
'git-amend': createAmendTool($, config),
|
|
9
|
+
'git-diff': createDiffTool($),
|
|
10
|
+
'git-log': createLogTool($),
|
|
11
|
+
'git-status': createStatusTool($),
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export default plugin;
|
|
16
|
+
//# sourceMappingURL=commits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commits.js","sourceRoot":"","sources":["../commits.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAEnH,MAAM,MAAM,GAAW,KAAK,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE;IAChD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,SAAS,CAAC,CAAA;IAE1C,OAAO;QACL,IAAI,EAAE;YACJ,YAAY,EAAE,gBAAgB,CAAC,CAAC,EAAE,MAAM,CAAC;YACzC,WAAW,EAAE,eAAe,CAAC,CAAC,EAAE,MAAM,CAAC;YACvC,UAAU,EAAE,cAAc,CAAC,CAAC,CAAC;YAC7B,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC;YAC3B,YAAY,EAAE,gBAAgB,CAAC,CAAC,CAAC;SAClC;KACF,CAAA;AACH,CAAC,CAAA;AAED,eAAe,MAAM,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const DEFAULT_TYPES: readonly ["feat", "fix", "build", "chore", "ci", "docs", "style", "refactor", "perf", "test", "revert"];
|
|
2
|
+
export declare const DEFAULT_MAX_LENGTH = 72;
|
|
3
|
+
export interface CommitsConfig {
|
|
4
|
+
types: string[];
|
|
5
|
+
scopes?: Record<string, string[]>;
|
|
6
|
+
maxLength: number;
|
|
7
|
+
}
|
|
8
|
+
export declare const loadConfig: (directory: string) => Promise<CommitsConfig>;
|
|
9
|
+
export declare const getAllScopes: (config: CommitsConfig) => string[] | undefined;
|
|
10
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,aAAa,yGAYhB,CAAA;AAEV,eAAO,MAAM,kBAAkB,KAAK,CAAA;AAQpC,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IACjC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,eAAO,MAAM,UAAU,GAAU,WAAW,MAAM,KAAG,OAAO,CAAC,aAAa,CAoBzE,CAAA;AAED,eAAO,MAAM,YAAY,GAAI,QAAQ,aAAa,KAAG,MAAM,EAAE,GAAG,SAI/D,CAAA"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { z } from 'zod/v4';
|
|
4
|
+
import { safeAsync } from "./utils/safe.js";
|
|
5
|
+
export const DEFAULT_TYPES = [
|
|
6
|
+
'feat',
|
|
7
|
+
'fix',
|
|
8
|
+
'build',
|
|
9
|
+
'chore',
|
|
10
|
+
'ci',
|
|
11
|
+
'docs',
|
|
12
|
+
'style',
|
|
13
|
+
'refactor',
|
|
14
|
+
'perf',
|
|
15
|
+
'test',
|
|
16
|
+
'revert',
|
|
17
|
+
];
|
|
18
|
+
export const DEFAULT_MAX_LENGTH = 72;
|
|
19
|
+
const rawConfigSchema = z.object({
|
|
20
|
+
types: z.array(z.string()).optional(),
|
|
21
|
+
scopes: z.record(z.string(), z.array(z.string())).optional(),
|
|
22
|
+
maxLength: z.number().optional(),
|
|
23
|
+
});
|
|
24
|
+
export const loadConfig = async (directory) => {
|
|
25
|
+
const configPath = join(directory, 'opencode-commits.json');
|
|
26
|
+
const result = await safeAsync(async () => {
|
|
27
|
+
const raw = await readFile(configPath, 'utf-8');
|
|
28
|
+
return rawConfigSchema.parse(JSON.parse(raw));
|
|
29
|
+
});
|
|
30
|
+
if (result.error) {
|
|
31
|
+
return {
|
|
32
|
+
types: [...DEFAULT_TYPES],
|
|
33
|
+
maxLength: DEFAULT_MAX_LENGTH,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
types: result.data.types ?? [...DEFAULT_TYPES],
|
|
38
|
+
scopes: result.data.scopes,
|
|
39
|
+
maxLength: result.data.maxLength ?? DEFAULT_MAX_LENGTH,
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
export const getAllScopes = (config) => {
|
|
43
|
+
if (!config.scopes)
|
|
44
|
+
return undefined;
|
|
45
|
+
return Object.values(config.scopes).flat();
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAA;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,MAAM;IACN,KAAK;IACL,OAAO;IACP,OAAO;IACP,IAAI;IACJ,MAAM;IACN,OAAO;IACP,UAAU;IACV,MAAM;IACN,MAAM;IACN,QAAQ;CACA,CAAA;AAEV,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAA;AAEpC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/B,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACrC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC5D,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACjC,CAAC,CAAA;AAQF,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAAE,SAAiB,EAA0B,EAAE;IAC5E,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,uBAAuB,CAAC,CAAA;IAE3D,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,KAAK,IAAI,EAAE;QACxC,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;QAC/C,OAAO,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;IAC/C,CAAC,CAAC,CAAA;IAEF,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,OAAO;YACL,KAAK,EAAE,CAAC,GAAG,aAAa,CAAC;YACzB,SAAS,EAAE,kBAAkB;SAC9B,CAAA;IACH,CAAC;IAED,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,GAAG,aAAa,CAAC;QAC9C,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM;QAC1B,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,IAAI,kBAAkB;KACvD,CAAA;AACH,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAqB,EAAwB,EAAE;IAC1E,IAAI,CAAC,MAAM,CAAC,MAAM;QAAE,OAAO,SAAS,CAAA;IAEpC,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;AAC5C,CAAC,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type CommitErrorKind = 'CommitMessageParseError' | 'CommitValidationError';
|
|
2
|
+
export interface CommitError extends Error {
|
|
3
|
+
kind: CommitErrorKind;
|
|
4
|
+
suggestions: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare const isCommitError: (error: unknown) => error is CommitError;
|
|
7
|
+
export declare const commitMessageParseError: (message: string, suggestions?: string[]) => CommitError;
|
|
8
|
+
export declare const commitValidationError: (message: string, suggestions?: string[]) => CommitError;
|
|
9
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,yBAAyB,GAAG,uBAAuB,CAAA;AAEjF,MAAM,WAAW,WAAY,SAAQ,KAAK;IACxC,IAAI,EAAE,eAAe,CAAA;IACrB,WAAW,EAAE,MAAM,EAAE,CAAA;CACtB;AAWD,eAAO,MAAM,aAAa,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,WAIvD,CAAA;AAED,eAAO,MAAM,uBAAuB,GAAI,SAAS,MAAM,EAAE,cAAa,MAAM,EAAO,KAAG,WAClB,CAAA;AAEpE,eAAO,MAAM,qBAAqB,GAAI,SAAS,MAAM,EAAE,cAAa,MAAM,EAAO,KAAG,WAClB,CAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const createCommitError = (kind, message, suggestions = []) => {
|
|
2
|
+
const error = new Error(message);
|
|
3
|
+
error.name = kind;
|
|
4
|
+
error.kind = kind;
|
|
5
|
+
error.suggestions = suggestions;
|
|
6
|
+
return error;
|
|
7
|
+
};
|
|
8
|
+
export const isCommitError = (error) => {
|
|
9
|
+
if (!(error instanceof Error))
|
|
10
|
+
return false;
|
|
11
|
+
return 'kind' in error && 'suggestions' in error;
|
|
12
|
+
};
|
|
13
|
+
export const commitMessageParseError = (message, suggestions = []) => createCommitError('CommitMessageParseError', message, suggestions);
|
|
14
|
+
export const commitValidationError = (message, suggestions = []) => createCommitError('CommitValidationError', message, suggestions);
|
|
15
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAOA,MAAM,iBAAiB,GAAG,CAAC,IAAqB,EAAE,OAAe,EAAE,cAAwB,EAAE,EAAe,EAAE;IAC5G,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,OAAO,CAAgB,CAAA;IAC/C,KAAK,CAAC,IAAI,GAAG,IAAI,CAAA;IACjB,KAAK,CAAC,IAAI,GAAG,IAAI,CAAA;IACjB,KAAK,CAAC,WAAW,GAAG,WAAW,CAAA;IAE/B,OAAO,KAAK,CAAA;AACd,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAc,EAAwB,EAAE;IACpE,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IAE3C,OAAO,MAAM,IAAI,KAAK,IAAI,aAAa,IAAI,KAAK,CAAA;AAClD,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,OAAe,EAAE,cAAwB,EAAE,EAAe,EAAE,CAClG,iBAAiB,CAAC,yBAAyB,EAAE,OAAO,EAAE,WAAW,CAAC,CAAA;AAEpE,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,OAAe,EAAE,cAAwB,EAAE,EAAe,EAAE,CAChG,iBAAiB,CAAC,uBAAuB,EAAE,OAAO,EAAE,WAAW,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/parser.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,GAAG,EAAE,MAAM,CAAA;CACZ;AA+DD,eAAO,MAAM,kBAAkB,GAAI,SAAS,MAAM,KAAG,mBA2CpD,CAAA"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { commitMessageParseError } from "./errors.js";
|
|
2
|
+
const parsePrefix = (prefix) => {
|
|
3
|
+
const parenOpen = prefix.indexOf('(');
|
|
4
|
+
const parenClose = prefix.indexOf(')');
|
|
5
|
+
if (parenOpen === -1 && parenClose === -1) {
|
|
6
|
+
return { type: prefix };
|
|
7
|
+
}
|
|
8
|
+
if (parenOpen === -1) {
|
|
9
|
+
throw commitMessageParseError('Found closing parenthesis without opening parenthesis', [
|
|
10
|
+
'Use the format: <type>(<scope>): <description>',
|
|
11
|
+
]);
|
|
12
|
+
}
|
|
13
|
+
if (parenClose === -1) {
|
|
14
|
+
throw commitMessageParseError('Found opening parenthesis without closing parenthesis', [
|
|
15
|
+
'Use the format: <type>(<scope>): <description>',
|
|
16
|
+
]);
|
|
17
|
+
}
|
|
18
|
+
if (parenClose < parenOpen) {
|
|
19
|
+
throw commitMessageParseError('Mismatched parentheses in commit message', [
|
|
20
|
+
'Use the format: <type>(<scope>): <description>',
|
|
21
|
+
]);
|
|
22
|
+
}
|
|
23
|
+
if (parenClose !== prefix.length - 1) {
|
|
24
|
+
throw commitMessageParseError('Unexpected characters after scope parentheses', [
|
|
25
|
+
'Use the format: <type>(<scope>): <description>',
|
|
26
|
+
]);
|
|
27
|
+
}
|
|
28
|
+
const type = prefix.slice(0, parenOpen);
|
|
29
|
+
const scope = prefix.slice(parenOpen + 1, parenClose);
|
|
30
|
+
if (!scope) {
|
|
31
|
+
throw commitMessageParseError('Scope must not be empty when parentheses are present', [
|
|
32
|
+
'Either provide a scope or remove the parentheses',
|
|
33
|
+
]);
|
|
34
|
+
}
|
|
35
|
+
if (!/^[a-z]/.test(scope)) {
|
|
36
|
+
throw commitMessageParseError('Scope must start with a lowercase letter', [
|
|
37
|
+
`Change "${scope}" to start with a lowercase letter`,
|
|
38
|
+
]);
|
|
39
|
+
}
|
|
40
|
+
if (!/^[a-z][a-zA-Z0-9-]*$/.test(scope)) {
|
|
41
|
+
throw commitMessageParseError('Scope must only contain letters, numbers, and hyphens', [
|
|
42
|
+
`Change "${scope}" to use only letters, numbers, and hyphens`,
|
|
43
|
+
]);
|
|
44
|
+
}
|
|
45
|
+
return { type, scope };
|
|
46
|
+
};
|
|
47
|
+
export const parseCommitMessage = (message) => {
|
|
48
|
+
const trimmed = message.trim();
|
|
49
|
+
if (!trimmed) {
|
|
50
|
+
throw commitMessageParseError('Commit message must not be empty', [
|
|
51
|
+
'Provide a message in the format: <type>[(<scope>)]: <description>',
|
|
52
|
+
]);
|
|
53
|
+
}
|
|
54
|
+
const colonIndex = trimmed.indexOf(':');
|
|
55
|
+
if (colonIndex === -1) {
|
|
56
|
+
throw commitMessageParseError('Commit message must contain a colon separator', [
|
|
57
|
+
'Use the format: <type>[(<scope>)]: <description>',
|
|
58
|
+
`Example: feat: ${trimmed}`,
|
|
59
|
+
]);
|
|
60
|
+
}
|
|
61
|
+
const prefix = trimmed.slice(0, colonIndex);
|
|
62
|
+
const description = trimmed.slice(colonIndex + 1).trim();
|
|
63
|
+
const { type, scope } = parsePrefix(prefix);
|
|
64
|
+
if (!type) {
|
|
65
|
+
throw commitMessageParseError('Commit type must not be empty', [
|
|
66
|
+
'Provide a type before the colon',
|
|
67
|
+
'Example: feat: add new feature',
|
|
68
|
+
]);
|
|
69
|
+
}
|
|
70
|
+
if (!/^[a-z]+$/.test(type)) {
|
|
71
|
+
throw commitMessageParseError('Commit type must contain only lowercase letters', [
|
|
72
|
+
`Change "${type}" to use only lowercase letters`,
|
|
73
|
+
'Valid types include: feat, fix, docs, style, refactor, test, chore',
|
|
74
|
+
]);
|
|
75
|
+
}
|
|
76
|
+
if (!description) {
|
|
77
|
+
throw commitMessageParseError('Commit description must not be empty', [
|
|
78
|
+
'Provide a description after the colon',
|
|
79
|
+
`Example: ${prefix}: add new feature`,
|
|
80
|
+
]);
|
|
81
|
+
}
|
|
82
|
+
return { type, scope, description, raw: trimmed };
|
|
83
|
+
};
|
|
84
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAcrD,MAAM,WAAW,GAAG,CAAC,MAAc,EAAe,EAAE;IAClD,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACrC,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAEtC,IAAI,SAAS,KAAK,CAAC,CAAC,IAAI,UAAU,KAAK,CAAC,CAAC,EAAE,CAAC;QAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;IACzB,CAAC;IAED,IAAI,SAAS,KAAK,CAAC,CAAC,EAAE,CAAC;QACrB,MAAM,uBAAuB,CAAC,uDAAuD,EAAE;YACrF,gDAAgD;SACjD,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,UAAU,KAAK,CAAC,CAAC,EAAE,CAAC;QACtB,MAAM,uBAAuB,CAAC,uDAAuD,EAAE;YACrF,gDAAgD;SACjD,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,UAAU,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,uBAAuB,CAAC,0CAA0C,EAAE;YACxE,gDAAgD;SACjD,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,UAAU,KAAK,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrC,MAAM,uBAAuB,CAAC,+CAA+C,EAAE;YAC7E,gDAAgD;SACjD,CAAC,CAAA;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAA;IACvC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,EAAE,UAAU,CAAC,CAAA;IAErD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,uBAAuB,CAAC,sDAAsD,EAAE;YACpF,kDAAkD;SACnD,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,uBAAuB,CAAC,0CAA0C,EAAE;YACxE,WAAW,KAAK,oCAAoC;SACrD,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACxC,MAAM,uBAAuB,CAAC,uDAAuD,EAAE;YACrF,WAAW,KAAK,6CAA6C;SAC9D,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;AACxB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,OAAe,EAAuB,EAAE;IACzE,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAA;IAE9B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,uBAAuB,CAAC,kCAAkC,EAAE;YAChE,mEAAmE;SACpE,CAAC,CAAA;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACvC,IAAI,UAAU,KAAK,CAAC,CAAC,EAAE,CAAC;QACtB,MAAM,uBAAuB,CAAC,+CAA+C,EAAE;YAC7E,kDAAkD;YAClD,kBAAkB,OAAO,EAAE;SAC5B,CAAC,CAAA;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAA;IAC3C,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IACxD,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,CAAA;IAE3C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,uBAAuB,CAAC,+BAA+B,EAAE;YAC7D,iCAAiC;YACjC,gCAAgC;SACjC,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,MAAM,uBAAuB,CAAC,iDAAiD,EAAE;YAC/E,WAAW,IAAI,iCAAiC;YAChD,oEAAoE;SACrE,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,uBAAuB,CAAC,sCAAsC,EAAE;YACpE,uCAAuC;YACvC,YAAY,MAAM,mBAAmB;SACtC,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,OAAO,EAAE,CAAA;AACnD,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/shell.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEtD,MAAM,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/shell.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { CommitsConfig } from './config.ts';
|
|
2
|
+
import type { BunShell } from './shell.ts';
|
|
3
|
+
export declare const formatValidationError: (error: Error) => string;
|
|
4
|
+
export declare const createCommitTool: ($: BunShell, config: CommitsConfig) => {
|
|
5
|
+
description: string;
|
|
6
|
+
args: {
|
|
7
|
+
message: import("zod").ZodString;
|
|
8
|
+
};
|
|
9
|
+
execute(args: {
|
|
10
|
+
message: string;
|
|
11
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
12
|
+
};
|
|
13
|
+
export declare const createAmendTool: ($: BunShell, config: CommitsConfig) => {
|
|
14
|
+
description: string;
|
|
15
|
+
args: {
|
|
16
|
+
message: import("zod").ZodString;
|
|
17
|
+
};
|
|
18
|
+
execute(args: {
|
|
19
|
+
message: string;
|
|
20
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
21
|
+
};
|
|
22
|
+
export declare const createDiffTool: ($: BunShell) => {
|
|
23
|
+
description: string;
|
|
24
|
+
args: {};
|
|
25
|
+
execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
26
|
+
};
|
|
27
|
+
export declare const createLogTool: ($: BunShell) => {
|
|
28
|
+
description: string;
|
|
29
|
+
args: {
|
|
30
|
+
count: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
31
|
+
};
|
|
32
|
+
execute(args: {
|
|
33
|
+
count?: number | undefined;
|
|
34
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
35
|
+
};
|
|
36
|
+
export declare const createStatusTool: ($: BunShell) => {
|
|
37
|
+
description: string;
|
|
38
|
+
args: {};
|
|
39
|
+
execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/tools.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEhD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAI1C,eAAO,MAAM,qBAAqB,GAAI,OAAO,KAAK,KAAG,MAYpD,CAAA;AAED,eAAO,MAAM,gBAAgB,GAAI,GAAG,QAAQ,EAAE,QAAQ,aAAa;;;;;;;;CAoBlE,CAAA;AAED,eAAO,MAAM,eAAe,GAAI,GAAG,QAAQ,EAAE,QAAQ,aAAa;;;;;;;;CAoBjE,CAAA;AAED,eAAO,MAAM,cAAc,GAAI,GAAG,QAAQ;;;;CAkBzC,CAAA;AAED,eAAO,MAAM,aAAa,GAAI,GAAG,QAAQ;;;;;;;;CAsBxC,CAAA;AAED,eAAO,MAAM,gBAAgB,GAAI,GAAG,QAAQ;;;;CAa3C,CAAA"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { tool } from '@opencode-ai/plugin';
|
|
2
|
+
import { isCommitError } from "./errors.js";
|
|
3
|
+
import { safe, safeAsync } from "./utils/safe.js";
|
|
4
|
+
import { validateCommitMessage } from "./validator.js";
|
|
5
|
+
export const formatValidationError = (error) => {
|
|
6
|
+
if (isCommitError(error)) {
|
|
7
|
+
let message = `**Error:** ${error.message}`;
|
|
8
|
+
if (error.suggestions.length > 0) {
|
|
9
|
+
message += '\n\n**Suggestions:**\n' + error.suggestions.map(s => `- ${s}`).join('\n');
|
|
10
|
+
}
|
|
11
|
+
return message;
|
|
12
|
+
}
|
|
13
|
+
return `**Error:** ${error.message}`;
|
|
14
|
+
};
|
|
15
|
+
export const createCommitTool = ($, config) => {
|
|
16
|
+
return tool({
|
|
17
|
+
description: 'Validate and commit staged changes with a conventional commit message',
|
|
18
|
+
args: {
|
|
19
|
+
message: tool.schema.string().describe('Conventional commit message (e.g. "feat(api): add endpoint")'),
|
|
20
|
+
},
|
|
21
|
+
async execute(args) {
|
|
22
|
+
const validation = safe(() => validateCommitMessage(args.message, config));
|
|
23
|
+
if (validation.error) {
|
|
24
|
+
return formatValidationError(validation.error);
|
|
25
|
+
}
|
|
26
|
+
const result = await safeAsync(() => $ `git commit -m ${args.message}`.text());
|
|
27
|
+
if (result.error) {
|
|
28
|
+
return `**Error:** Failed to commit staged changes: ${result.error.message}`;
|
|
29
|
+
}
|
|
30
|
+
return `**Committed successfully**\n\n\`\`\`\n${result.data.trim()}\n\`\`\``;
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
export const createAmendTool = ($, config) => {
|
|
35
|
+
return tool({
|
|
36
|
+
description: 'Amend the last commit with a new validated conventional commit message',
|
|
37
|
+
args: {
|
|
38
|
+
message: tool.schema.string().describe('New conventional commit message'),
|
|
39
|
+
},
|
|
40
|
+
async execute(args) {
|
|
41
|
+
const validation = safe(() => validateCommitMessage(args.message, config));
|
|
42
|
+
if (validation.error) {
|
|
43
|
+
return formatValidationError(validation.error);
|
|
44
|
+
}
|
|
45
|
+
const result = await safeAsync(() => $ `git commit --amend -m ${args.message}`.text());
|
|
46
|
+
if (result.error) {
|
|
47
|
+
return `**Error:** Failed to amend commit: ${result.error.message}`;
|
|
48
|
+
}
|
|
49
|
+
return `**Amended successfully**\n\n\`\`\`\n${result.data.trim()}\n\`\`\``;
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
export const createDiffTool = ($) => {
|
|
54
|
+
return tool({
|
|
55
|
+
description: 'Show the currently staged diff',
|
|
56
|
+
args: {},
|
|
57
|
+
async execute() {
|
|
58
|
+
const result = await safeAsync(() => $ `git diff --staged`.text());
|
|
59
|
+
if (result.error) {
|
|
60
|
+
return `**Error:** Failed to get staged diff: ${result.error.message}`;
|
|
61
|
+
}
|
|
62
|
+
const trimmed = result.data.trim();
|
|
63
|
+
if (!trimmed) {
|
|
64
|
+
return 'Nothing is currently staged.';
|
|
65
|
+
}
|
|
66
|
+
return `\`\`\`diff\n${trimmed}\n\`\`\``;
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
export const createLogTool = ($) => {
|
|
71
|
+
return tool({
|
|
72
|
+
description: 'List recent commits',
|
|
73
|
+
args: {
|
|
74
|
+
count: tool.schema.number().optional().describe('Number of commits to show (default: 10)'),
|
|
75
|
+
},
|
|
76
|
+
async execute(args) {
|
|
77
|
+
const count = args.count ?? 10;
|
|
78
|
+
const result = await safeAsync(() => $ `git log --oneline -n ${count}`.text());
|
|
79
|
+
if (result.error) {
|
|
80
|
+
return `**Error:** Failed to get git log: ${result.error.message}`;
|
|
81
|
+
}
|
|
82
|
+
const trimmed = result.data.trim();
|
|
83
|
+
if (!trimmed) {
|
|
84
|
+
return 'No commits found.';
|
|
85
|
+
}
|
|
86
|
+
return `\`\`\`\n${trimmed}\n\`\`\``;
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
export const createStatusTool = ($) => {
|
|
91
|
+
return tool({
|
|
92
|
+
description: 'Show the working tree status including staged, unstaged, and untracked files',
|
|
93
|
+
args: {},
|
|
94
|
+
async execute() {
|
|
95
|
+
const result = await safeAsync(() => $ `git status`.text());
|
|
96
|
+
if (result.error) {
|
|
97
|
+
return `**Error:** Failed to get git status: ${result.error.message}`;
|
|
98
|
+
}
|
|
99
|
+
return `\`\`\`\n${result.data.trim()}\n\`\`\``;
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type ResultSuccess<T> = {
|
|
2
|
+
data: T;
|
|
3
|
+
error: null;
|
|
4
|
+
};
|
|
5
|
+
type ResultError<E> = {
|
|
6
|
+
data: null;
|
|
7
|
+
error: E;
|
|
8
|
+
};
|
|
9
|
+
export type Result<T, E> = ResultSuccess<T> | ResultError<E>;
|
|
10
|
+
type SafeResult<T> = Result<T, Error>;
|
|
11
|
+
export declare const safe: <T>(fn: () => T) => SafeResult<T>;
|
|
12
|
+
export declare const safeAsync: <T>(fn: () => Promise<T>) => Promise<SafeResult<T>>;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=safe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safe.d.ts","sourceRoot":"","sources":["../../../src/utils/safe.ts"],"names":[],"mappings":"AAAA,KAAK,aAAa,CAAC,CAAC,IAAI;IACtB,IAAI,EAAE,CAAC,CAAA;IACP,KAAK,EAAE,IAAI,CAAA;CACZ,CAAA;AAED,KAAK,WAAW,CAAC,CAAC,IAAI;IACpB,IAAI,EAAE,IAAI,CAAA;IACV,KAAK,EAAE,CAAC,CAAA;CACT,CAAA;AAED,MAAM,MAAM,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;AAE5D,KAAK,UAAU,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;AAErC,eAAO,MAAM,IAAI,GAAI,CAAC,EAAE,IAAI,MAAM,CAAC,KAAG,UAAU,CAAC,CAAC,CAajD,CAAA;AAED,eAAO,MAAM,SAAS,GAAU,CAAC,EAAE,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,KAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAa9E,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const safe = (fn) => {
|
|
2
|
+
try {
|
|
3
|
+
const data = fn();
|
|
4
|
+
return {
|
|
5
|
+
data,
|
|
6
|
+
error: null,
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
return {
|
|
11
|
+
data: null,
|
|
12
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
export const safeAsync = async (fn) => {
|
|
17
|
+
try {
|
|
18
|
+
const data = await fn();
|
|
19
|
+
return {
|
|
20
|
+
data,
|
|
21
|
+
error: null,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
return {
|
|
26
|
+
data: null,
|
|
27
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=safe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safe.js","sourceRoot":"","sources":["../../../src/utils/safe.ts"],"names":[],"mappings":"AAcA,MAAM,CAAC,MAAM,IAAI,GAAG,CAAI,EAAW,EAAiB,EAAE;IACpD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,EAAE,EAAE,CAAA;QACjB,OAAO;YACL,IAAI;YACJ,KAAK,EAAE,IAAI;SACZ,CAAA;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,IAAI,EAAE,IAAI;YACV,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;SACjE,CAAA;IACH,CAAC;AACH,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAAK,EAAoB,EAA0B,EAAE;IACjF,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,EAAE,EAAE,CAAA;QACvB,OAAO;YACL,IAAI;YACJ,KAAK,EAAE,IAAI;SACZ,CAAA;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,IAAI,EAAE,IAAI;YACV,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;SACjE,CAAA;IACH,CAAC;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAKhD,eAAO,MAAM,qBAAqB,GAAI,SAAS,MAAM,EAAE,QAAQ,aAAa,KAAG,IAyC9E,CAAA"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getAllScopes } from "./config.js";
|
|
2
|
+
import { commitValidationError } from "./errors.js";
|
|
3
|
+
import { parseCommitMessage } from "./parser.js";
|
|
4
|
+
export const validateCommitMessage = (message, config) => {
|
|
5
|
+
const parsed = parseCommitMessage(message);
|
|
6
|
+
if (!config.types.includes(parsed.type)) {
|
|
7
|
+
const firstChar = parsed.type[0];
|
|
8
|
+
const close = firstChar ? config.types.filter(t => t.startsWith(firstChar)) : [];
|
|
9
|
+
const suggestions = close.length > 0
|
|
10
|
+
? [`Did you mean: ${close.join(', ')}?`]
|
|
11
|
+
: [`Valid types are: ${config.types.join(', ')}`];
|
|
12
|
+
throw commitValidationError(`Invalid commit type: "${parsed.type}"`, suggestions);
|
|
13
|
+
}
|
|
14
|
+
const allowedScopes = getAllScopes(config);
|
|
15
|
+
if (parsed.scope && allowedScopes) {
|
|
16
|
+
if (!allowedScopes.includes(parsed.scope)) {
|
|
17
|
+
throw commitValidationError(`Invalid scope: "${parsed.scope}"`, [
|
|
18
|
+
`Allowed scopes are: ${allowedScopes.join(', ')}`,
|
|
19
|
+
]);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (/^[A-Z]/.test(parsed.description)) {
|
|
23
|
+
throw commitValidationError('Description must start with a lowercase letter', [
|
|
24
|
+
`Change "${parsed.description}" to start with a lowercase letter`,
|
|
25
|
+
]);
|
|
26
|
+
}
|
|
27
|
+
if (/[.!,;:]$/.test(parsed.description)) {
|
|
28
|
+
throw commitValidationError('Description must not end with punctuation', [
|
|
29
|
+
`Remove the trailing "${parsed.description.slice(-1)}" from the description`,
|
|
30
|
+
]);
|
|
31
|
+
}
|
|
32
|
+
if (parsed.raw.length > config.maxLength) {
|
|
33
|
+
throw commitValidationError(`Commit message exceeds ${config.maxLength} characters (${parsed.raw.length})`, ['Be more concise']);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/validator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAEhD,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,OAAe,EAAE,MAAqB,EAAQ,EAAE;IACpF,MAAM,MAAM,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAA;IAE1C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAChC,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QAEhF,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC;YAClC,CAAC,CAAC,CAAC,iBAAiB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;YACxC,CAAC,CAAC,CAAC,oBAAoB,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAEnD,MAAM,qBAAqB,CAAC,yBAAyB,MAAM,CAAC,IAAI,GAAG,EAAE,WAAW,CAAC,CAAA;IACnF,CAAC;IAED,MAAM,aAAa,GAAG,YAAY,CAAC,MAAM,CAAC,CAAA;IAC1C,IAAI,MAAM,CAAC,KAAK,IAAI,aAAa,EAAE,CAAC;QAClC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1C,MAAM,qBAAqB,CAAC,mBAAmB,MAAM,CAAC,KAAK,GAAG,EAAE;gBAC9D,uBAAuB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;aAClD,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;QACtC,MAAM,qBAAqB,CAAC,gDAAgD,EAAE;YAC5E,WAAW,MAAM,CAAC,WAAW,oCAAoC;SAClE,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;QACxC,MAAM,qBAAqB,CAAC,2CAA2C,EAAE;YACvE,wBAAwB,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,wBAAwB;SAC7E,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;QACzC,MAAM,qBAAqB,CACzB,0BAA0B,MAAM,CAAC,SAAS,gBAAgB,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,EAC9E,CAAC,iBAAiB,CAAC,CACpB,CAAA;IACH,CAAC;AACH,CAAC,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-commits",
|
|
3
|
+
"version": "0.0.1-next.0",
|
|
4
|
+
"description": "An OpenCode plugin that enforces Conventional Commits by validating commit messages before they reach git",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"opencode",
|
|
7
|
+
"opencode-plugin",
|
|
8
|
+
"plugin",
|
|
9
|
+
"git",
|
|
10
|
+
"commits",
|
|
11
|
+
"conventional-commits"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/whaaaley/opencode-commits#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/whaaaley/opencode-commits/issues"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/whaaaley/opencode-commits.git"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"author": "Dustin Dowell",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"import": "./dist/commits.js",
|
|
27
|
+
"types": "./dist/commits.d.ts"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"main": "./dist/commits.js",
|
|
31
|
+
"types": "./dist/commits.d.ts",
|
|
32
|
+
"files": [
|
|
33
|
+
"dist/",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "npm run clean && tsc",
|
|
39
|
+
"clean": "rm -rf dist",
|
|
40
|
+
"fmt": "bun run dprint fmt",
|
|
41
|
+
"prepublishOnly": "npm run build",
|
|
42
|
+
"test": "bun test"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"zod": "4.1.8"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@opencode-ai/plugin": "1.1.12",
|
|
49
|
+
"@types/bun": "latest",
|
|
50
|
+
"dprint": "^0.49.1",
|
|
51
|
+
"typescript": "5.9.3"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"@opencode-ai/plugin": ">=0.13.7"
|
|
55
|
+
}
|
|
56
|
+
}
|