@commitguard/cli 0.0.13 → 0.0.14
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 +167 -0
- package/dist/index.mjs +373 -333
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# CommitGuard
|
|
2
|
+
|
|
3
|
+
Protect your codebase with every commit. CommitGuard automatically analyzes your code for security vulnerabilities, performance issues, and code quality problems before they enter your repository. Grab your free API key at https://commitguard.ai.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Get CommitGuard running in under 60 seconds. It works seamlessly with any git repository and integrates automatically with VSCode and other git clients.
|
|
8
|
+
|
|
9
|
+
### Quick Start
|
|
10
|
+
|
|
11
|
+
**1. Install CommitGuard globally**
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g @commitguard/cli
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**2. Navigate to your project**
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
cd your-project
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**3. Initialize CommitGuard**
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
commitguard init
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**4. Commit as usual**
|
|
30
|
+
|
|
31
|
+
CommitGuard now runs automatically on every commit. It works with all git integrations including VSCode, terminal, and other git clients.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
git commit -m "Add new feature"
|
|
35
|
+
[CommitGuard] Analyzing commit...
|
|
36
|
+
✓ Commit passed all checks
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
### Bypassing Checks
|
|
42
|
+
|
|
43
|
+
To bypass CommitGuard checks for a specific commit, add `--skip` to your commit message:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
git commit -m "Add new feature --skip"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The `--skip` flag is automatically removed from the final commit message. When using git in your terminal, you'll be prompted to confirm if you want to bypass checks.
|
|
50
|
+
|
|
51
|
+
### Configuring Checks
|
|
52
|
+
|
|
53
|
+
View or update CommitGuard preferences for your repository:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
commitguard config
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
On the Pro plan, you can add custom rules through the config comand.
|
|
60
|
+
|
|
61
|
+
## CLI Commands
|
|
62
|
+
|
|
63
|
+
### `commitguard init`
|
|
64
|
+
|
|
65
|
+
Install CommitGuard in your current repository.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
$ commitguard init
|
|
69
|
+
✓ CommitGuard installed successfully
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### `commitguard remove`
|
|
73
|
+
|
|
74
|
+
Remove CommitGuard from the current repository.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
$ commitguard remove
|
|
78
|
+
✓ CommitGuard removed
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### `commitguard config`
|
|
82
|
+
|
|
83
|
+
View or update CommitGuard preferences for the current repository. Pro plan users can add custom rules.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
$ commitguard config
|
|
87
|
+
Select enabled checks for this project:
|
|
88
|
+
Security, Performance, Code Quality, Architecture
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### `commitguard keys`
|
|
92
|
+
|
|
93
|
+
Manage your global API key for CommitGuard.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
$ commitguard keys
|
|
97
|
+
Current API key: sk-ant-***************
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## What Happens After Installation?
|
|
101
|
+
|
|
102
|
+
- Every commit is analyzed before it's created.
|
|
103
|
+
- No config files are added to your project
|
|
104
|
+
- Bypass checks anytime with `--skip` in your commit message
|
|
105
|
+
- Works seamlessly with all git clients and IDEs
|
|
106
|
+
|
|
107
|
+
## Troubleshooting
|
|
108
|
+
|
|
109
|
+
<details>
|
|
110
|
+
<summary><strong>CommitGuard isn't running on commits</strong></summary>
|
|
111
|
+
|
|
112
|
+
Try reinstalling the hooks:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
commitguard remove
|
|
116
|
+
commitguard init
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Verify that the hooks are installed:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
ls -la .git/hooks/
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
You should see a `pre-commit` hook file.
|
|
126
|
+
</details>
|
|
127
|
+
|
|
128
|
+
<details>
|
|
129
|
+
<summary><strong>How do I skip a single commit?</strong></summary>
|
|
130
|
+
|
|
131
|
+
Add `--skip` to your commit message:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
git commit -m "Emergency fix --skip"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The `--skip` flag is automatically removed from the final message.
|
|
138
|
+
</details>
|
|
139
|
+
|
|
140
|
+
<details>
|
|
141
|
+
<summary><strong>How do I completely remove CommitGuard?</strong></summary>
|
|
142
|
+
|
|
143
|
+
Remove the hooks and uninstall the package:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
commitguard remove
|
|
147
|
+
npm uninstall -g @commitguard/cli
|
|
148
|
+
```
|
|
149
|
+
</details>
|
|
150
|
+
|
|
151
|
+
## Features
|
|
152
|
+
|
|
153
|
+
- **Security Analysis** - Detect vulnerabilities before they reach your repo
|
|
154
|
+
- **Performance Checks** - Identify performance bottlenecks early
|
|
155
|
+
- **Code Quality** - Maintain consistent code standards
|
|
156
|
+
- **Architecture Review** - Ensure architectural patterns are followed
|
|
157
|
+
- **Zero Configuration** - Works out of the box
|
|
158
|
+
- **Universal Compatibility** - Works with any git workflow
|
|
159
|
+
-- **Custom Rules** - Add your own custom checks to the code reviews
|
|
160
|
+
|
|
161
|
+
## Support
|
|
162
|
+
|
|
163
|
+
For issues, feature requests, or questions related to this package please open an issue or email us at hello@commitguard.ai
|
|
164
|
+
|
|
165
|
+
## License
|
|
166
|
+
|
|
167
|
+
MIT
|
package/dist/index.mjs
CHANGED
|
@@ -3,12 +3,12 @@ import process from "node:process";
|
|
|
3
3
|
import { consola } from "consola";
|
|
4
4
|
import updateNotifier from "update-notifier";
|
|
5
5
|
import { execFileSync, execSync } from "node:child_process";
|
|
6
|
-
import { createHash } from "node:crypto";
|
|
7
|
-
import { cancel, confirm, intro, isCancel, log, multiselect, note, outro, text } from "@clack/prompts";
|
|
8
|
-
import { Entry } from "@napi-rs/keyring";
|
|
9
6
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
10
7
|
import { homedir } from "node:os";
|
|
11
8
|
import { dirname, join } from "node:path";
|
|
9
|
+
import { cancel, confirm, intro, isCancel, log, multiselect, note, outro, select, text } from "@clack/prompts";
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
import { Entry } from "@napi-rs/keyring";
|
|
12
12
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
13
13
|
import { readFile } from "node:fs/promises";
|
|
14
14
|
import { findUp } from "find-up";
|
|
@@ -17,7 +17,7 @@ import stringWidth from "string-width";
|
|
|
17
17
|
import "dotenv/config";
|
|
18
18
|
|
|
19
19
|
//#region package.json
|
|
20
|
-
var version = "0.0.
|
|
20
|
+
var version = "0.0.14";
|
|
21
21
|
var package_default = {
|
|
22
22
|
name: "@commitguard/cli",
|
|
23
23
|
type: "module",
|
|
@@ -71,6 +71,277 @@ var package_default = {
|
|
|
71
71
|
}
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/utils/global.ts
|
|
76
|
+
function createDiffHash(diff) {
|
|
77
|
+
return createHash("md5").update(diff).digest("base64url");
|
|
78
|
+
}
|
|
79
|
+
function addGitLineNumbers(diff) {
|
|
80
|
+
if (!diff.trim()) return diff;
|
|
81
|
+
const lines = diff.split("\n");
|
|
82
|
+
const result = [];
|
|
83
|
+
let oldLine = 0;
|
|
84
|
+
let newLine = 0;
|
|
85
|
+
for (const line of lines) if (line.startsWith("@@")) {
|
|
86
|
+
const match = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
87
|
+
if (match) {
|
|
88
|
+
oldLine = Number.parseInt(match[1], 10);
|
|
89
|
+
newLine = Number.parseInt(match[2], 10);
|
|
90
|
+
}
|
|
91
|
+
result.push(line);
|
|
92
|
+
} else if (line.startsWith("---") || line.startsWith("+++") || line.startsWith("diff ") || line.startsWith("index ")) result.push(line);
|
|
93
|
+
else if (line.startsWith("-")) {
|
|
94
|
+
result.push(`${oldLine}:${line}`);
|
|
95
|
+
oldLine++;
|
|
96
|
+
} else if (line.startsWith("+")) {
|
|
97
|
+
result.push(`${newLine}:${line}`);
|
|
98
|
+
newLine++;
|
|
99
|
+
} else {
|
|
100
|
+
result.push(`${newLine}:${line}`);
|
|
101
|
+
oldLine++;
|
|
102
|
+
newLine++;
|
|
103
|
+
}
|
|
104
|
+
return result.join("\n");
|
|
105
|
+
}
|
|
106
|
+
const MESSAGES = { noGit: "No .git folder found. Run this inside a git repository." };
|
|
107
|
+
|
|
108
|
+
//#endregion
|
|
109
|
+
//#region src/utils/config.ts
|
|
110
|
+
const MAX_CUSTOM_PROMPT_LENGTH = 500;
|
|
111
|
+
const CONFIG_DIR = join(homedir(), ".commitguard");
|
|
112
|
+
const PROJECTS_CONFIG_PATH = join(CONFIG_DIR, "projects.json");
|
|
113
|
+
let projectsConfigCache = null;
|
|
114
|
+
const GIT_DIR$1 = ".git";
|
|
115
|
+
function ensureConfigDir() {
|
|
116
|
+
if (!existsSync(CONFIG_DIR)) try {
|
|
117
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
118
|
+
} catch (e) {
|
|
119
|
+
consola.error(`Failed to create config directory at ${CONFIG_DIR}: ${e.message}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function getDefaultConfig() {
|
|
123
|
+
return {
|
|
124
|
+
context: "normal",
|
|
125
|
+
checks: {
|
|
126
|
+
security: true,
|
|
127
|
+
performance: true,
|
|
128
|
+
codeQuality: true,
|
|
129
|
+
architecture: true
|
|
130
|
+
},
|
|
131
|
+
severityLevels: {
|
|
132
|
+
critical: true,
|
|
133
|
+
warning: true,
|
|
134
|
+
suggestion: false
|
|
135
|
+
},
|
|
136
|
+
customRule: ""
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
let projectIdCache = null;
|
|
140
|
+
function getProjectId() {
|
|
141
|
+
if (projectIdCache) return projectIdCache;
|
|
142
|
+
try {
|
|
143
|
+
projectIdCache = execFileSync("git", [
|
|
144
|
+
"rev-list",
|
|
145
|
+
"--max-parents=0",
|
|
146
|
+
"HEAD"
|
|
147
|
+
], {
|
|
148
|
+
encoding: "utf8",
|
|
149
|
+
stdio: [
|
|
150
|
+
"pipe",
|
|
151
|
+
"pipe",
|
|
152
|
+
"ignore"
|
|
153
|
+
]
|
|
154
|
+
}).trim().split("\n")[0];
|
|
155
|
+
return projectIdCache;
|
|
156
|
+
} catch {
|
|
157
|
+
consola.error("Warning: Unable to determine project ID. Using current working directory as fallback project ID.");
|
|
158
|
+
projectIdCache = process.cwd();
|
|
159
|
+
return projectIdCache;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function loadProjectsConfig() {
|
|
163
|
+
if (projectsConfigCache) return projectsConfigCache;
|
|
164
|
+
if (existsSync(PROJECTS_CONFIG_PATH)) try {
|
|
165
|
+
const content = readFileSync(PROJECTS_CONFIG_PATH, "utf8");
|
|
166
|
+
projectsConfigCache = JSON.parse(content);
|
|
167
|
+
return projectsConfigCache;
|
|
168
|
+
} catch {
|
|
169
|
+
consola.warn("Failed to parse projects config");
|
|
170
|
+
}
|
|
171
|
+
projectsConfigCache = {};
|
|
172
|
+
return projectsConfigCache;
|
|
173
|
+
}
|
|
174
|
+
function saveProjectsConfig(projects) {
|
|
175
|
+
try {
|
|
176
|
+
ensureConfigDir();
|
|
177
|
+
writeFileSync(PROJECTS_CONFIG_PATH, JSON.stringify(projects, null, 2));
|
|
178
|
+
projectsConfigCache = projects;
|
|
179
|
+
} catch (e) {
|
|
180
|
+
consola.error(`Failed to save projects config: ${e.message}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function loadConfig() {
|
|
184
|
+
const projectId = getProjectId();
|
|
185
|
+
return loadProjectsConfig()[projectId] || getDefaultConfig();
|
|
186
|
+
}
|
|
187
|
+
async function manageConfig() {
|
|
188
|
+
if (!existsSync(GIT_DIR$1)) {
|
|
189
|
+
cancel(MESSAGES.noGit);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const projectId = getProjectId();
|
|
193
|
+
const currentConfig = loadConfig();
|
|
194
|
+
intro(`CommitGuard Configuration`);
|
|
195
|
+
const enabledChecks = await multiselect({
|
|
196
|
+
message: "Select enabled checks for this project:",
|
|
197
|
+
options: [
|
|
198
|
+
{
|
|
199
|
+
value: "security",
|
|
200
|
+
label: "Security"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
value: "performance",
|
|
204
|
+
label: "Performance"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
value: "codeQuality",
|
|
208
|
+
label: "Code Quality"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
value: "architecture",
|
|
212
|
+
label: "Architecture"
|
|
213
|
+
}
|
|
214
|
+
],
|
|
215
|
+
initialValues: Object.entries(currentConfig.checks).filter(([_, enabled]) => enabled).map(([key]) => key)
|
|
216
|
+
});
|
|
217
|
+
if (isCancel(enabledChecks)) {
|
|
218
|
+
cancel("Configuration cancelled");
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const enabledSeverity = await multiselect({
|
|
222
|
+
message: "Select severity levels for enabled checks:",
|
|
223
|
+
options: [
|
|
224
|
+
{
|
|
225
|
+
value: "suggestion",
|
|
226
|
+
label: "Suggestion"
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
value: "warning",
|
|
230
|
+
label: "Warning"
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
value: "critical",
|
|
234
|
+
label: "Critical"
|
|
235
|
+
}
|
|
236
|
+
],
|
|
237
|
+
initialValues: Object.entries(currentConfig.severityLevels).filter(([_, enabled]) => enabled).map(([key]) => key)
|
|
238
|
+
});
|
|
239
|
+
if (isCancel(enabledSeverity)) {
|
|
240
|
+
cancel("Configuration cancelled");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const contextLevel = await select({
|
|
244
|
+
message: "Select context level for analysis:",
|
|
245
|
+
options: [{
|
|
246
|
+
value: "minimal",
|
|
247
|
+
label: "Minimal (Just Actual Changes)"
|
|
248
|
+
}, {
|
|
249
|
+
value: "normal",
|
|
250
|
+
label: "Normal (Actual Changes + Context Lines)"
|
|
251
|
+
}],
|
|
252
|
+
initialValue: currentConfig.context
|
|
253
|
+
});
|
|
254
|
+
if (isCancel(contextLevel)) {
|
|
255
|
+
cancel("Configuration cancelled");
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
let customRule = currentConfig.customRule;
|
|
259
|
+
if (currentConfig.customRule) {
|
|
260
|
+
log.info(`Current custom rule: ${currentConfig.customRule}`);
|
|
261
|
+
const editCustomRule = await confirm({
|
|
262
|
+
message: "Would you like to edit the custom rule? (Currently only available to pro users)",
|
|
263
|
+
initialValue: false
|
|
264
|
+
});
|
|
265
|
+
if (isCancel(editCustomRule)) {
|
|
266
|
+
cancel("Configuration cancelled");
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (editCustomRule) {
|
|
270
|
+
const newCustomRule = await text({
|
|
271
|
+
message: "Enter new custom rule (leave empty to remove):",
|
|
272
|
+
initialValue: currentConfig.customRule,
|
|
273
|
+
validate: (value) => {
|
|
274
|
+
const val = String(value).trim();
|
|
275
|
+
if (!val) return void 0;
|
|
276
|
+
if (val.length > MAX_CUSTOM_PROMPT_LENGTH) return `Custom rule must be ${MAX_CUSTOM_PROMPT_LENGTH} characters or less (current: ${val.length})`;
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
if (isCancel(newCustomRule)) {
|
|
280
|
+
cancel("Configuration cancelled");
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
customRule = String(newCustomRule).trim();
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
const addCustomRule = await confirm({
|
|
287
|
+
message: "Would you like to add a custom rule for this project? (Currently only available to pro users)",
|
|
288
|
+
initialValue: false
|
|
289
|
+
});
|
|
290
|
+
if (isCancel(addCustomRule)) {
|
|
291
|
+
cancel("Configuration cancelled");
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (addCustomRule) {
|
|
295
|
+
const newCustomRule = await text({
|
|
296
|
+
message: "Enter custom rule (leave empty to skip):",
|
|
297
|
+
placeholder: "e.g., Check for proper error handling in async functions",
|
|
298
|
+
validate: (value) => {
|
|
299
|
+
const val = String(value).trim();
|
|
300
|
+
if (!val) return void 0;
|
|
301
|
+
if (val.length > MAX_CUSTOM_PROMPT_LENGTH) return `Custom rule must be ${MAX_CUSTOM_PROMPT_LENGTH} characters or less (current: ${val.length})`;
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
if (isCancel(newCustomRule)) {
|
|
305
|
+
cancel("Configuration cancelled");
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
customRule = String(newCustomRule).trim();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const newConfig = {
|
|
312
|
+
context: contextLevel,
|
|
313
|
+
checks: {
|
|
314
|
+
security: enabledChecks.includes("security"),
|
|
315
|
+
performance: enabledChecks.includes("performance"),
|
|
316
|
+
codeQuality: enabledChecks.includes("codeQuality"),
|
|
317
|
+
architecture: enabledChecks.includes("architecture")
|
|
318
|
+
},
|
|
319
|
+
severityLevels: {
|
|
320
|
+
suggestion: enabledSeverity.includes("suggestion"),
|
|
321
|
+
warning: enabledSeverity.includes("warning"),
|
|
322
|
+
critical: enabledSeverity.includes("critical")
|
|
323
|
+
},
|
|
324
|
+
customRule
|
|
325
|
+
};
|
|
326
|
+
if (JSON.stringify(newConfig) === JSON.stringify(currentConfig)) {
|
|
327
|
+
outro("No changes made to the configuration.");
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const confirmUpdate = await confirm({ message: "Save this configuration?" });
|
|
331
|
+
if (isCancel(confirmUpdate)) {
|
|
332
|
+
cancel("Configuration cancelled");
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (!confirmUpdate) {
|
|
336
|
+
outro("Configuration not saved.");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const projects = loadProjectsConfig();
|
|
340
|
+
projects[projectId] = newConfig;
|
|
341
|
+
saveProjectsConfig(projects);
|
|
342
|
+
outro("✓ Configuration updated for this project!");
|
|
343
|
+
}
|
|
344
|
+
|
|
74
345
|
//#endregion
|
|
75
346
|
//#region src/data/ignore.json
|
|
76
347
|
var ignore = [
|
|
@@ -163,80 +434,47 @@ var ignore = [
|
|
|
163
434
|
"*.svg",
|
|
164
435
|
"*.webp",
|
|
165
436
|
"*.avif",
|
|
166
|
-
"*.ico",
|
|
167
|
-
"*.mp4",
|
|
168
|
-
"*.webm",
|
|
169
|
-
"*.mov",
|
|
170
|
-
"*.mp3",
|
|
171
|
-
"*.wav",
|
|
172
|
-
"*.ogg",
|
|
173
|
-
"*.flac",
|
|
174
|
-
"*.snap",
|
|
175
|
-
"__snapshots__/**",
|
|
176
|
-
"playwright-report/**",
|
|
177
|
-
"test-results/**",
|
|
178
|
-
".nyc_output/**",
|
|
179
|
-
"docs/.vitepress/dist/**",
|
|
180
|
-
"storybook-static/**",
|
|
181
|
-
".DS_Store",
|
|
182
|
-
"Thumbs.db",
|
|
183
|
-
".idea/**",
|
|
184
|
-
".vscode/**",
|
|
185
|
-
"*.swp",
|
|
186
|
-
"*.dump",
|
|
187
|
-
"*.dmp",
|
|
188
|
-
"coverage/**",
|
|
189
|
-
"tmp/**",
|
|
190
|
-
"temp/**",
|
|
191
|
-
"*.lock",
|
|
192
|
-
"*.sqlite",
|
|
193
|
-
"*.db",
|
|
194
|
-
"*.rdb"
|
|
195
|
-
];
|
|
196
|
-
|
|
197
|
-
//#endregion
|
|
198
|
-
//#region src/utils/global.ts
|
|
199
|
-
function createDiffHash(diff) {
|
|
200
|
-
return createHash("md5").update(diff).digest("base64url");
|
|
201
|
-
}
|
|
202
|
-
function addGitLineNumbers(diff) {
|
|
203
|
-
if (!diff.trim()) return diff;
|
|
204
|
-
const lines = diff.split("\n");
|
|
205
|
-
const result = [];
|
|
206
|
-
let oldLine = 0;
|
|
207
|
-
let newLine = 0;
|
|
208
|
-
for (const line of lines) if (line.startsWith("@@")) {
|
|
209
|
-
const match = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
210
|
-
if (match) {
|
|
211
|
-
oldLine = Number.parseInt(match[1], 10);
|
|
212
|
-
newLine = Number.parseInt(match[2], 10);
|
|
213
|
-
}
|
|
214
|
-
result.push(line);
|
|
215
|
-
} else if (line.startsWith("---") || line.startsWith("+++") || line.startsWith("diff ") || line.startsWith("index ")) result.push(line);
|
|
216
|
-
else if (line.startsWith("-")) {
|
|
217
|
-
result.push(`${oldLine}:${line}`);
|
|
218
|
-
oldLine++;
|
|
219
|
-
} else if (line.startsWith("+")) {
|
|
220
|
-
result.push(`${newLine}:${line}`);
|
|
221
|
-
newLine++;
|
|
222
|
-
} else {
|
|
223
|
-
result.push(`${newLine}:${line}`);
|
|
224
|
-
oldLine++;
|
|
225
|
-
newLine++;
|
|
226
|
-
}
|
|
227
|
-
return result.join("\n");
|
|
228
|
-
}
|
|
229
|
-
const MESSAGES = { noGit: "No .git folder found. Run this inside a git repository." };
|
|
437
|
+
"*.ico",
|
|
438
|
+
"*.mp4",
|
|
439
|
+
"*.webm",
|
|
440
|
+
"*.mov",
|
|
441
|
+
"*.mp3",
|
|
442
|
+
"*.wav",
|
|
443
|
+
"*.ogg",
|
|
444
|
+
"*.flac",
|
|
445
|
+
"*.snap",
|
|
446
|
+
"__snapshots__/**",
|
|
447
|
+
"playwright-report/**",
|
|
448
|
+
"test-results/**",
|
|
449
|
+
".nyc_output/**",
|
|
450
|
+
"docs/.vitepress/dist/**",
|
|
451
|
+
"storybook-static/**",
|
|
452
|
+
".DS_Store",
|
|
453
|
+
"Thumbs.db",
|
|
454
|
+
".idea/**",
|
|
455
|
+
".vscode/**",
|
|
456
|
+
"*.swp",
|
|
457
|
+
"*.dump",
|
|
458
|
+
"*.dmp",
|
|
459
|
+
"coverage/**",
|
|
460
|
+
"tmp/**",
|
|
461
|
+
"temp/**",
|
|
462
|
+
"*.lock",
|
|
463
|
+
"*.sqlite",
|
|
464
|
+
"*.db",
|
|
465
|
+
"*.rdb"
|
|
466
|
+
];
|
|
230
467
|
|
|
231
468
|
//#endregion
|
|
232
469
|
//#region src/utils/git.ts
|
|
233
|
-
function getStagedDiff() {
|
|
470
|
+
function getStagedDiff(context) {
|
|
471
|
+
const gitContextCommand = context === "minimal" ? [] : ["--function-context"];
|
|
234
472
|
try {
|
|
235
473
|
return addGitLineNumbers(execFileSync("git", [
|
|
236
474
|
"diff",
|
|
237
475
|
"--cached",
|
|
238
476
|
"--no-color",
|
|
239
|
-
|
|
477
|
+
...gitContextCommand,
|
|
240
478
|
"--diff-algorithm=histogram",
|
|
241
479
|
"--diff-filter=AMC",
|
|
242
480
|
"--",
|
|
@@ -255,14 +493,15 @@ function getStagedDiff() {
|
|
|
255
493
|
return "";
|
|
256
494
|
}
|
|
257
495
|
}
|
|
258
|
-
function getLastDiff() {
|
|
496
|
+
function getLastDiff(context) {
|
|
497
|
+
const gitContextCommand = context === "minimal" ? [] : ["--function-context"];
|
|
259
498
|
try {
|
|
260
499
|
return execFileSync("git", [
|
|
261
500
|
"diff",
|
|
262
501
|
"HEAD~1",
|
|
263
502
|
"HEAD",
|
|
264
503
|
"--no-color",
|
|
265
|
-
|
|
504
|
+
...gitContextCommand,
|
|
266
505
|
"--diff-algorithm=histogram",
|
|
267
506
|
"--diff-filter=AMC",
|
|
268
507
|
"--",
|
|
@@ -347,274 +586,75 @@ async function manageGlobalKey() {
|
|
|
347
586
|
|
|
348
587
|
//#endregion
|
|
349
588
|
//#region src/utils/api.ts
|
|
350
|
-
|
|
589
|
+
const DEFAULT_TIMEOUT = 2e4;
|
|
590
|
+
async function sendToCommitGuard(diff, eslint, config$1) {
|
|
351
591
|
const apiKey = process.env.COMMITGUARD_API_KEY || getGlobalKey() || null;
|
|
352
592
|
if (!apiKey) throw new Error("No API key found. Set one globally with \"commitguard keys\" or add COMMITGUARD_API_KEY to your .env file. Get your free API key at https://commitguard.ai");
|
|
353
593
|
const apiUrl = process.env.COMMITGUARD_API_URL || "https://api.commitguard.ai/v1/analyze";
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
"
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
594
|
+
const controller = new AbortController();
|
|
595
|
+
const timeoutId = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT);
|
|
596
|
+
try {
|
|
597
|
+
const response = await fetch(apiUrl, {
|
|
598
|
+
method: "POST",
|
|
599
|
+
headers: {
|
|
600
|
+
"Content-Type": "application/json",
|
|
601
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
602
|
+
"User-Agent": "commitguard-cli"
|
|
603
|
+
},
|
|
604
|
+
body: JSON.stringify({
|
|
605
|
+
diff,
|
|
606
|
+
eslint,
|
|
607
|
+
config: config$1
|
|
608
|
+
}),
|
|
609
|
+
signal: controller.signal
|
|
610
|
+
});
|
|
611
|
+
clearTimeout(timeoutId);
|
|
612
|
+
if (!response.ok) {
|
|
613
|
+
const errorText = await response.text();
|
|
614
|
+
let errorMessage = "Failed to analyze commit";
|
|
615
|
+
if (response.status === 401) errorMessage = "Invalid API key. Check your key with \"commitguard keys\" or get a new one at https://commitguard.ai";
|
|
616
|
+
else if (response.status === 429) errorMessage = "Rate limit exceeded. Please try again later";
|
|
617
|
+
else if (response.status === 500) errorMessage = "CommitGuard service error. Please try again later";
|
|
618
|
+
else if (response.status >= 400 && response.status < 500) errorMessage = `Request error: ${errorText || "Invalid request"}`;
|
|
619
|
+
else errorMessage = `Service unavailable (${response.status}). Please try again later`;
|
|
620
|
+
throw new Error(errorMessage);
|
|
621
|
+
}
|
|
622
|
+
return await response.json();
|
|
623
|
+
} catch (error) {
|
|
624
|
+
clearTimeout(timeoutId);
|
|
625
|
+
if (error instanceof Error && error.name === "AbortError") throw new Error(`Request timed out after ${DEFAULT_TIMEOUT}ms`);
|
|
626
|
+
throw error;
|
|
376
627
|
}
|
|
377
|
-
return await response.json();
|
|
378
628
|
}
|
|
379
629
|
async function bypassCommitGuard() {
|
|
380
630
|
const apiKey = process.env.COMMITGUARD_API_KEY || getGlobalKey() || null;
|
|
381
631
|
if (!apiKey) throw new Error("No API key found. Set one globally with \"commitguard keys\" or add COMMITGUARD_API_KEY to your .env file. Get your free API key at https://commitguard.ai");
|
|
382
632
|
const apiUrl = process.env.COMMITGUARD_API_BYPASS_URL || "https://api.commitguard.ai/v1/bypass";
|
|
383
|
-
const diff = getLastDiff();
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
headers: {
|
|
387
|
-
"Content-Type": "application/json",
|
|
388
|
-
"Authorization": `Bearer ${apiKey}`,
|
|
389
|
-
"User-Agent": "commitguard-cli"
|
|
390
|
-
},
|
|
391
|
-
body: JSON.stringify({ diff })
|
|
392
|
-
});
|
|
393
|
-
if (!response.ok) {
|
|
394
|
-
const errorText = await response.text();
|
|
395
|
-
throw new Error(`API request failed (${response.status}): ${errorText}`);
|
|
396
|
-
}
|
|
397
|
-
return await response.json();
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
//#endregion
|
|
401
|
-
//#region src/utils/config.ts
|
|
402
|
-
const MAX_CUSTOM_PROMPT_LENGTH = 500;
|
|
403
|
-
const CONFIG_DIR = join(homedir(), ".commitguard");
|
|
404
|
-
const PROJECTS_CONFIG_PATH = join(CONFIG_DIR, "projects.json");
|
|
405
|
-
let projectsConfigCache = null;
|
|
406
|
-
const GIT_DIR$1 = ".git";
|
|
407
|
-
function ensureConfigDir() {
|
|
408
|
-
if (!existsSync(CONFIG_DIR)) try {
|
|
409
|
-
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
410
|
-
} catch (e) {
|
|
411
|
-
consola.error(`Failed to create config directory at ${CONFIG_DIR}: ${e.message}`);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
function getDefaultConfig() {
|
|
415
|
-
return {
|
|
416
|
-
checks: {
|
|
417
|
-
security: true,
|
|
418
|
-
performance: true,
|
|
419
|
-
codeQuality: true,
|
|
420
|
-
architecture: true
|
|
421
|
-
},
|
|
422
|
-
severityLevels: {
|
|
423
|
-
critical: true,
|
|
424
|
-
warning: true,
|
|
425
|
-
suggestion: false
|
|
426
|
-
},
|
|
427
|
-
customRule: ""
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
let projectIdCache = null;
|
|
431
|
-
function getProjectId() {
|
|
432
|
-
if (projectIdCache) return projectIdCache;
|
|
433
|
-
try {
|
|
434
|
-
projectIdCache = execFileSync("git", [
|
|
435
|
-
"rev-list",
|
|
436
|
-
"--max-parents=0",
|
|
437
|
-
"HEAD"
|
|
438
|
-
], {
|
|
439
|
-
encoding: "utf8",
|
|
440
|
-
stdio: [
|
|
441
|
-
"pipe",
|
|
442
|
-
"pipe",
|
|
443
|
-
"ignore"
|
|
444
|
-
]
|
|
445
|
-
}).trim().split("\n")[0];
|
|
446
|
-
return projectIdCache;
|
|
447
|
-
} catch {
|
|
448
|
-
consola.error("Warning: Unable to determine project ID. Using current working directory as fallback project ID.");
|
|
449
|
-
projectIdCache = process.cwd();
|
|
450
|
-
return projectIdCache;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
function loadProjectsConfig() {
|
|
454
|
-
if (projectsConfigCache) return projectsConfigCache;
|
|
455
|
-
if (existsSync(PROJECTS_CONFIG_PATH)) try {
|
|
456
|
-
const content = readFileSync(PROJECTS_CONFIG_PATH, "utf8");
|
|
457
|
-
projectsConfigCache = JSON.parse(content);
|
|
458
|
-
return projectsConfigCache;
|
|
459
|
-
} catch {
|
|
460
|
-
consola.warn("Failed to parse projects config");
|
|
461
|
-
}
|
|
462
|
-
projectsConfigCache = {};
|
|
463
|
-
return projectsConfigCache;
|
|
464
|
-
}
|
|
465
|
-
function saveProjectsConfig(projects) {
|
|
633
|
+
const diff = getLastDiff(loadConfig().context);
|
|
634
|
+
const controller = new AbortController();
|
|
635
|
+
const timeoutId = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT);
|
|
466
636
|
try {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
474
|
-
function loadConfig() {
|
|
475
|
-
const projectId = getProjectId();
|
|
476
|
-
return loadProjectsConfig()[projectId] || getDefaultConfig();
|
|
477
|
-
}
|
|
478
|
-
async function manageConfig() {
|
|
479
|
-
if (!existsSync(GIT_DIR$1)) {
|
|
480
|
-
cancel(MESSAGES.noGit);
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
const projectId = getProjectId();
|
|
484
|
-
const currentConfig = loadConfig();
|
|
485
|
-
intro(`CommitGuard Configuration`);
|
|
486
|
-
const enabledChecks = await multiselect({
|
|
487
|
-
message: "Select enabled checks for this project:",
|
|
488
|
-
options: [
|
|
489
|
-
{
|
|
490
|
-
value: "security",
|
|
491
|
-
label: "Security"
|
|
492
|
-
},
|
|
493
|
-
{
|
|
494
|
-
value: "performance",
|
|
495
|
-
label: "Performance"
|
|
496
|
-
},
|
|
497
|
-
{
|
|
498
|
-
value: "codeQuality",
|
|
499
|
-
label: "Code Quality"
|
|
500
|
-
},
|
|
501
|
-
{
|
|
502
|
-
value: "architecture",
|
|
503
|
-
label: "Architecture"
|
|
504
|
-
}
|
|
505
|
-
],
|
|
506
|
-
initialValues: Object.entries(currentConfig.checks).filter(([_, enabled]) => enabled).map(([key]) => key)
|
|
507
|
-
});
|
|
508
|
-
if (isCancel(enabledChecks)) {
|
|
509
|
-
cancel("Configuration cancelled");
|
|
510
|
-
return;
|
|
511
|
-
}
|
|
512
|
-
const enabledSeverity = await multiselect({
|
|
513
|
-
message: "Select severity levels for enabled checks:",
|
|
514
|
-
options: [
|
|
515
|
-
{
|
|
516
|
-
value: "suggestion",
|
|
517
|
-
label: "Suggestion"
|
|
637
|
+
const response = await fetch(apiUrl, {
|
|
638
|
+
method: "POST",
|
|
639
|
+
headers: {
|
|
640
|
+
"Content-Type": "application/json",
|
|
641
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
642
|
+
"User-Agent": "commitguard-cli"
|
|
518
643
|
},
|
|
519
|
-
{
|
|
520
|
-
|
|
521
|
-
label: "Warning"
|
|
522
|
-
},
|
|
523
|
-
{
|
|
524
|
-
value: "critical",
|
|
525
|
-
label: "Critical"
|
|
526
|
-
}
|
|
527
|
-
],
|
|
528
|
-
initialValues: Object.entries(currentConfig.severityLevels).filter(([_, enabled]) => enabled).map(([key]) => key)
|
|
529
|
-
});
|
|
530
|
-
if (isCancel(enabledSeverity)) {
|
|
531
|
-
cancel("Configuration cancelled");
|
|
532
|
-
return;
|
|
533
|
-
}
|
|
534
|
-
let customRule = currentConfig.customRule;
|
|
535
|
-
if (currentConfig.customRule) {
|
|
536
|
-
consola.info(`Current custom rule: ${currentConfig.customRule}`);
|
|
537
|
-
const editCustomRule = await confirm({
|
|
538
|
-
message: "Would you like to edit the custom rule? (Currently only available to pro users)",
|
|
539
|
-
initialValue: false
|
|
540
|
-
});
|
|
541
|
-
if (isCancel(editCustomRule)) {
|
|
542
|
-
cancel("Configuration cancelled");
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
if (editCustomRule) {
|
|
546
|
-
const newCustomRule = await text({
|
|
547
|
-
message: "Enter new custom rule (leave empty to remove):",
|
|
548
|
-
initialValue: currentConfig.customRule,
|
|
549
|
-
validate: (value) => {
|
|
550
|
-
const val = String(value).trim();
|
|
551
|
-
if (!val) return void 0;
|
|
552
|
-
if (val.length > MAX_CUSTOM_PROMPT_LENGTH) return `Custom rule must be ${MAX_CUSTOM_PROMPT_LENGTH} characters or less (current: ${val.length})`;
|
|
553
|
-
}
|
|
554
|
-
});
|
|
555
|
-
if (isCancel(newCustomRule)) {
|
|
556
|
-
cancel("Configuration cancelled");
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
customRule = String(newCustomRule).trim();
|
|
560
|
-
}
|
|
561
|
-
} else {
|
|
562
|
-
const addCustomRule = await confirm({
|
|
563
|
-
message: "Would you like to add a custom rule for this project? (Currently only available to pro users)",
|
|
564
|
-
initialValue: false
|
|
644
|
+
body: JSON.stringify({ diff }),
|
|
645
|
+
signal: controller.signal
|
|
565
646
|
});
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
if (addCustomRule) {
|
|
571
|
-
const newCustomRule = await text({
|
|
572
|
-
message: "Enter custom rule (leave empty to skip):",
|
|
573
|
-
placeholder: "e.g., Check for proper error handling in async functions",
|
|
574
|
-
validate: (value) => {
|
|
575
|
-
const val = String(value).trim();
|
|
576
|
-
if (!val) return void 0;
|
|
577
|
-
if (val.length > MAX_CUSTOM_PROMPT_LENGTH) return `Custom rule must be ${MAX_CUSTOM_PROMPT_LENGTH} characters or less (current: ${val.length})`;
|
|
578
|
-
}
|
|
579
|
-
});
|
|
580
|
-
if (isCancel(newCustomRule)) {
|
|
581
|
-
cancel("Configuration cancelled");
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
customRule = String(newCustomRule).trim();
|
|
647
|
+
clearTimeout(timeoutId);
|
|
648
|
+
if (!response.ok) {
|
|
649
|
+
const errorText = await response.text();
|
|
650
|
+
throw new Error(`API request failed (${response.status}): ${errorText}`);
|
|
585
651
|
}
|
|
652
|
+
return await response.json();
|
|
653
|
+
} catch (error) {
|
|
654
|
+
clearTimeout(timeoutId);
|
|
655
|
+
if (error instanceof Error && error.name === "AbortError") throw new Error(`Request timed out after ${DEFAULT_TIMEOUT}ms`);
|
|
656
|
+
throw error;
|
|
586
657
|
}
|
|
587
|
-
const newConfig = {
|
|
588
|
-
checks: {
|
|
589
|
-
security: enabledChecks.includes("security"),
|
|
590
|
-
performance: enabledChecks.includes("performance"),
|
|
591
|
-
codeQuality: enabledChecks.includes("codeQuality"),
|
|
592
|
-
architecture: enabledChecks.includes("architecture")
|
|
593
|
-
},
|
|
594
|
-
severityLevels: {
|
|
595
|
-
suggestion: enabledSeverity.includes("suggestion"),
|
|
596
|
-
warning: enabledSeverity.includes("warning"),
|
|
597
|
-
critical: enabledSeverity.includes("critical")
|
|
598
|
-
},
|
|
599
|
-
customRule
|
|
600
|
-
};
|
|
601
|
-
if (JSON.stringify(newConfig) === JSON.stringify(currentConfig)) {
|
|
602
|
-
outro("No changes made to the configuration.");
|
|
603
|
-
return;
|
|
604
|
-
}
|
|
605
|
-
const confirmUpdate = await confirm({ message: "Save this configuration?" });
|
|
606
|
-
if (isCancel(confirmUpdate)) {
|
|
607
|
-
cancel("Configuration cancelled");
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
if (!confirmUpdate) {
|
|
611
|
-
outro("Configuration not saved.");
|
|
612
|
-
return;
|
|
613
|
-
}
|
|
614
|
-
const projects = loadProjectsConfig();
|
|
615
|
-
projects[projectId] = newConfig;
|
|
616
|
-
saveProjectsConfig(projects);
|
|
617
|
-
outro("✓ Configuration updated for this project!");
|
|
618
658
|
}
|
|
619
659
|
|
|
620
660
|
//#endregion
|
|
@@ -676,13 +716,13 @@ async function getEslintRules({ startDir = process.cwd(), overrideCache = false
|
|
|
676
716
|
} catch {}
|
|
677
717
|
return null;
|
|
678
718
|
});
|
|
679
|
-
const config = (await Promise.all(loaders)).find((r) => r !== null) ?? {
|
|
719
|
+
const config$1 = (await Promise.all(loaders)).find((r) => r !== null) ?? {
|
|
680
720
|
rules: {},
|
|
681
721
|
source: null
|
|
682
722
|
};
|
|
683
|
-
cache.setKey(cacheKey, config);
|
|
723
|
+
cache.setKey(cacheKey, config$1);
|
|
684
724
|
cache.save(true);
|
|
685
|
-
return config;
|
|
725
|
+
return config$1;
|
|
686
726
|
}
|
|
687
727
|
|
|
688
728
|
//#endregion
|
|
@@ -845,6 +885,7 @@ async function removeHooks() {
|
|
|
845
885
|
//#endregion
|
|
846
886
|
//#region src/utils/staged.ts
|
|
847
887
|
const CACHE_PATH = join(".git", "commitguard-cache.json");
|
|
888
|
+
const config = loadConfig();
|
|
848
889
|
const CATEGORY_LABELS = {
|
|
849
890
|
security: "🚨 [SECURITY]",
|
|
850
891
|
performance: "🚀 [PERFORMANCE]",
|
|
@@ -903,7 +944,7 @@ function groupIssuesByFile(issues = []) {
|
|
|
903
944
|
};
|
|
904
945
|
}
|
|
905
946
|
async function onStaged() {
|
|
906
|
-
const diff = getStagedDiff();
|
|
947
|
+
const diff = getStagedDiff(config.context);
|
|
907
948
|
if (!diff.trim()) {
|
|
908
949
|
clearCache();
|
|
909
950
|
return;
|
|
@@ -912,7 +953,6 @@ async function onStaged() {
|
|
|
912
953
|
const existingCache = readCache();
|
|
913
954
|
if (existingCache && existingCache.hash === diffHash) return;
|
|
914
955
|
try {
|
|
915
|
-
const config = loadConfig();
|
|
916
956
|
const response = await sendToCommitGuard(diff, (await getEslintRules()).rules, config);
|
|
917
957
|
writeCache({
|
|
918
958
|
hash: diffHash,
|
|
@@ -925,7 +965,7 @@ async function onStaged() {
|
|
|
925
965
|
}
|
|
926
966
|
}
|
|
927
967
|
function getCachedAnalysis(diff, diffHash) {
|
|
928
|
-
const effectiveDiff = diff ?? getStagedDiff();
|
|
968
|
+
const effectiveDiff = diff ?? getStagedDiff(config.context);
|
|
929
969
|
if (!effectiveDiff.trim()) return {
|
|
930
970
|
analysis: {
|
|
931
971
|
status: "pass",
|
|
@@ -944,7 +984,7 @@ function getCachedAnalysis(diff, diffHash) {
|
|
|
944
984
|
};
|
|
945
985
|
}
|
|
946
986
|
async function validateCommit() {
|
|
947
|
-
const diff = getStagedDiff();
|
|
987
|
+
const diff = getStagedDiff(config.context);
|
|
948
988
|
const diffHash = diff.trim() ? createDiffHash(diff) : "";
|
|
949
989
|
const cached = getCachedAnalysis(diff, diffHash);
|
|
950
990
|
if (!cached) {
|