@compilr-dev/agents-coding 0.0.1
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 +788 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +75 -0
- package/dist/skills/index.d.ts +39 -0
- package/dist/skills/index.js +322 -0
- package/dist/tools/git/branch.d.ts +17 -0
- package/dist/tools/git/branch.js +264 -0
- package/dist/tools/git/commit.d.ts +23 -0
- package/dist/tools/git/commit.js +280 -0
- package/dist/tools/git/diff.d.ts +19 -0
- package/dist/tools/git/diff.js +221 -0
- package/dist/tools/git/index.d.ts +10 -0
- package/dist/tools/git/index.js +11 -0
- package/dist/tools/git/log.d.ts +19 -0
- package/dist/tools/git/log.js +235 -0
- package/dist/tools/git/stash.d.ts +17 -0
- package/dist/tools/git/stash.js +294 -0
- package/dist/tools/git/status.d.ts +19 -0
- package/dist/tools/git/status.js +160 -0
- package/dist/tools/git/types.d.ts +293 -0
- package/dist/tools/git/types.js +4 -0
- package/dist/tools/git/utils.d.ts +58 -0
- package/dist/tools/git/utils.js +197 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.js +5 -0
- package/dist/tools/project/detect.d.ts +19 -0
- package/dist/tools/project/detect.js +341 -0
- package/dist/tools/project/find-root.d.ts +21 -0
- package/dist/tools/project/find-root.js +239 -0
- package/dist/tools/project/index.d.ts +6 -0
- package/dist/tools/project/index.js +5 -0
- package/dist/tools/project/types.d.ts +83 -0
- package/dist/tools/project/types.js +4 -0
- package/dist/tools/runners/build.d.ts +19 -0
- package/dist/tools/runners/build.js +306 -0
- package/dist/tools/runners/format.d.ts +19 -0
- package/dist/tools/runners/format.js +376 -0
- package/dist/tools/runners/index.d.ts +9 -0
- package/dist/tools/runners/index.js +9 -0
- package/dist/tools/runners/lint.d.ts +19 -0
- package/dist/tools/runners/lint.js +356 -0
- package/dist/tools/runners/test.d.ts +19 -0
- package/dist/tools/runners/test.js +386 -0
- package/dist/tools/runners/types.d.ts +97 -0
- package/dist/tools/runners/types.js +4 -0
- package/dist/tools/runners/utils.d.ts +69 -0
- package/dist/tools/runners/utils.js +179 -0
- package/dist/tools/search/definition.d.ts +19 -0
- package/dist/tools/search/definition.js +305 -0
- package/dist/tools/search/index.d.ts +8 -0
- package/dist/tools/search/index.js +8 -0
- package/dist/tools/search/references.d.ts +19 -0
- package/dist/tools/search/references.js +179 -0
- package/dist/tools/search/todos.d.ts +19 -0
- package/dist/tools/search/todos.js +269 -0
- package/dist/tools/search/types.d.ts +132 -0
- package/dist/tools/search/types.js +4 -0
- package/dist/tools/search/utils.d.ts +45 -0
- package/dist/tools/search/utils.js +152 -0
- package/package.json +88 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Branch Tool
|
|
3
|
+
* List, create, delete, switch, and rename branches
|
|
4
|
+
*/
|
|
5
|
+
import { defineTool, createSuccessResult, createErrorResult } from '@compilr-dev/agents';
|
|
6
|
+
import { runGitCommand, isGitRepository, isDirectory } from './utils.js';
|
|
7
|
+
/**
|
|
8
|
+
* Git branch tool
|
|
9
|
+
*/
|
|
10
|
+
export const gitBranchTool = defineTool({
|
|
11
|
+
name: 'git_branch',
|
|
12
|
+
description: 'Manage git branches. Actions: list (show all branches), create (new branch), ' +
|
|
13
|
+
'delete (remove branch), switch (checkout branch), rename (change branch name).',
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
path: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'Working directory path (default: current directory)',
|
|
20
|
+
},
|
|
21
|
+
action: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
enum: ['list', 'create', 'delete', 'switch', 'rename'],
|
|
24
|
+
description: 'Action to perform',
|
|
25
|
+
},
|
|
26
|
+
name: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'Branch name (for create/delete/switch/rename)',
|
|
29
|
+
},
|
|
30
|
+
newName: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
description: 'New branch name (for rename action)',
|
|
33
|
+
},
|
|
34
|
+
remotes: {
|
|
35
|
+
type: 'boolean',
|
|
36
|
+
description: 'Include remote branches in list',
|
|
37
|
+
},
|
|
38
|
+
force: {
|
|
39
|
+
type: 'boolean',
|
|
40
|
+
description: 'Force delete even if not merged',
|
|
41
|
+
},
|
|
42
|
+
startPoint: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
description: 'Start point for new branch (commit/branch)',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
required: ['action'],
|
|
48
|
+
},
|
|
49
|
+
execute: executeGitBranch,
|
|
50
|
+
});
|
|
51
|
+
/**
|
|
52
|
+
* Execute git branch
|
|
53
|
+
*/
|
|
54
|
+
async function executeGitBranch(input) {
|
|
55
|
+
const targetPath = input.path ?? process.cwd();
|
|
56
|
+
// Check if path exists
|
|
57
|
+
if (!(await isDirectory(targetPath))) {
|
|
58
|
+
return createErrorResult(`Directory not found: ${targetPath}`);
|
|
59
|
+
}
|
|
60
|
+
// Check if it's a git repository
|
|
61
|
+
if (!(await isGitRepository(targetPath))) {
|
|
62
|
+
return createErrorResult(`Not a git repository: ${targetPath}`);
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
switch (input.action) {
|
|
66
|
+
case 'list':
|
|
67
|
+
return await listBranches(targetPath, input.remotes ?? false);
|
|
68
|
+
case 'create':
|
|
69
|
+
if (!input.name) {
|
|
70
|
+
return createErrorResult('Branch name is required for create action');
|
|
71
|
+
}
|
|
72
|
+
return await createBranch(targetPath, input.name, input.startPoint);
|
|
73
|
+
case 'delete':
|
|
74
|
+
if (!input.name) {
|
|
75
|
+
return createErrorResult('Branch name is required for delete action');
|
|
76
|
+
}
|
|
77
|
+
return await deleteBranch(targetPath, input.name, input.force ?? false);
|
|
78
|
+
case 'switch':
|
|
79
|
+
if (!input.name) {
|
|
80
|
+
return createErrorResult('Branch name is required for switch action');
|
|
81
|
+
}
|
|
82
|
+
return await switchBranch(targetPath, input.name);
|
|
83
|
+
case 'rename':
|
|
84
|
+
if (!input.name || !input.newName) {
|
|
85
|
+
return createErrorResult('Both name and newName are required for rename action');
|
|
86
|
+
}
|
|
87
|
+
return await renameBranch(targetPath, input.name, input.newName);
|
|
88
|
+
default:
|
|
89
|
+
return createErrorResult(`Unknown action: ${String(input.action)}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
return createErrorResult(error instanceof Error ? error.message : String(error));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* List branches
|
|
98
|
+
*/
|
|
99
|
+
async function listBranches(repoPath, includeRemotes) {
|
|
100
|
+
// Get current branch
|
|
101
|
+
const currentResult = await runGitCommand(['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
102
|
+
cwd: repoPath,
|
|
103
|
+
});
|
|
104
|
+
const currentBranch = currentResult.success ? currentResult.stdout : '';
|
|
105
|
+
// List branches with format
|
|
106
|
+
const args = ['branch', '--format=%(refname:short)\t%(upstream:short)\t%(objectname:short)'];
|
|
107
|
+
if (includeRemotes) {
|
|
108
|
+
args.push('-a');
|
|
109
|
+
}
|
|
110
|
+
const result = await runGitCommand(args, { cwd: repoPath });
|
|
111
|
+
if (!result.success) {
|
|
112
|
+
return createErrorResult(`Failed to list branches: ${result.stderr}`);
|
|
113
|
+
}
|
|
114
|
+
const branches = [];
|
|
115
|
+
for (const line of result.stdout.split('\n').filter(Boolean)) {
|
|
116
|
+
const [name, tracking, lastCommit] = line.split('\t');
|
|
117
|
+
const isRemote = name.startsWith('remotes/') || name.includes('/');
|
|
118
|
+
branches.push({
|
|
119
|
+
name: isRemote ? name.replace(/^remotes\//, '') : name,
|
|
120
|
+
isRemote,
|
|
121
|
+
isCurrent: name === currentBranch,
|
|
122
|
+
tracking: tracking || undefined,
|
|
123
|
+
lastCommit: lastCommit || undefined,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const branchResult = {
|
|
127
|
+
current: currentBranch,
|
|
128
|
+
branches,
|
|
129
|
+
message: `Found ${String(branches.length)} branch(es)`,
|
|
130
|
+
};
|
|
131
|
+
return createSuccessResult(branchResult);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Create a new branch
|
|
135
|
+
*/
|
|
136
|
+
async function createBranch(repoPath, name, startPoint) {
|
|
137
|
+
const args = ['branch', name];
|
|
138
|
+
if (startPoint) {
|
|
139
|
+
args.push(startPoint);
|
|
140
|
+
}
|
|
141
|
+
const result = await runGitCommand(args, { cwd: repoPath });
|
|
142
|
+
if (!result.success) {
|
|
143
|
+
return createErrorResult(`Failed to create branch: ${result.stderr}`);
|
|
144
|
+
}
|
|
145
|
+
const branchResult = {
|
|
146
|
+
message: `Created branch '${name}'${startPoint ? ` from '${startPoint}'` : ''}`,
|
|
147
|
+
};
|
|
148
|
+
return createSuccessResult(branchResult);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Delete a branch
|
|
152
|
+
*/
|
|
153
|
+
async function deleteBranch(repoPath, name, force) {
|
|
154
|
+
// Check if trying to delete current branch
|
|
155
|
+
const currentResult = await runGitCommand(['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
156
|
+
cwd: repoPath,
|
|
157
|
+
});
|
|
158
|
+
if (currentResult.success && currentResult.stdout === name) {
|
|
159
|
+
return createErrorResult(`Cannot delete the currently checked out branch '${name}'`);
|
|
160
|
+
}
|
|
161
|
+
const args = ['branch', force ? '-D' : '-d', name];
|
|
162
|
+
const result = await runGitCommand(args, { cwd: repoPath });
|
|
163
|
+
if (!result.success) {
|
|
164
|
+
if (result.stderr.includes('not fully merged')) {
|
|
165
|
+
return createErrorResult(`Branch '${name}' is not fully merged. Use force=true to delete anyway.`);
|
|
166
|
+
}
|
|
167
|
+
return createErrorResult(`Failed to delete branch: ${result.stderr}`);
|
|
168
|
+
}
|
|
169
|
+
const branchResult = {
|
|
170
|
+
message: `Deleted branch '${name}'`,
|
|
171
|
+
};
|
|
172
|
+
return createSuccessResult(branchResult);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Switch to a branch
|
|
176
|
+
*/
|
|
177
|
+
async function switchBranch(repoPath, name) {
|
|
178
|
+
// First try 'git switch' (Git 2.23+), fall back to 'git checkout'
|
|
179
|
+
let result = await runGitCommand(['switch', name], { cwd: repoPath });
|
|
180
|
+
if (!result.success && result.stderr.includes('not a git command')) {
|
|
181
|
+
// Fall back to checkout for older Git versions
|
|
182
|
+
result = await runGitCommand(['checkout', name], { cwd: repoPath });
|
|
183
|
+
}
|
|
184
|
+
if (!result.success) {
|
|
185
|
+
// Check if branch exists
|
|
186
|
+
const checkResult = await runGitCommand(['rev-parse', '--verify', name], { cwd: repoPath });
|
|
187
|
+
if (!checkResult.success) {
|
|
188
|
+
return createErrorResult(`Branch '${name}' does not exist`);
|
|
189
|
+
}
|
|
190
|
+
return createErrorResult(`Failed to switch branch: ${result.stderr}`);
|
|
191
|
+
}
|
|
192
|
+
const branchResult = {
|
|
193
|
+
current: name,
|
|
194
|
+
message: `Switched to branch '${name}'`,
|
|
195
|
+
};
|
|
196
|
+
return createSuccessResult(branchResult);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Rename a branch
|
|
200
|
+
*/
|
|
201
|
+
async function renameBranch(repoPath, oldName, newName) {
|
|
202
|
+
const args = ['branch', '-m', oldName, newName];
|
|
203
|
+
const result = await runGitCommand(args, { cwd: repoPath });
|
|
204
|
+
if (!result.success) {
|
|
205
|
+
return createErrorResult(`Failed to rename branch: ${result.stderr}`);
|
|
206
|
+
}
|
|
207
|
+
const branchResult = {
|
|
208
|
+
message: `Renamed branch '${oldName}' to '${newName}'`,
|
|
209
|
+
};
|
|
210
|
+
return createSuccessResult(branchResult);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Factory function to create git branch tool with custom options
|
|
214
|
+
*/
|
|
215
|
+
export function createGitBranchTool(options) {
|
|
216
|
+
return defineTool({
|
|
217
|
+
name: 'git_branch',
|
|
218
|
+
description: 'Manage git branches. Actions: list, create, delete, switch, rename.',
|
|
219
|
+
inputSchema: {
|
|
220
|
+
type: 'object',
|
|
221
|
+
properties: {
|
|
222
|
+
path: {
|
|
223
|
+
type: 'string',
|
|
224
|
+
description: 'Working directory path (default: current directory)',
|
|
225
|
+
},
|
|
226
|
+
action: {
|
|
227
|
+
type: 'string',
|
|
228
|
+
enum: ['list', 'create', 'delete', 'switch', 'rename'],
|
|
229
|
+
description: 'Action to perform',
|
|
230
|
+
},
|
|
231
|
+
name: {
|
|
232
|
+
type: 'string',
|
|
233
|
+
description: 'Branch name',
|
|
234
|
+
},
|
|
235
|
+
newName: {
|
|
236
|
+
type: 'string',
|
|
237
|
+
description: 'New branch name (for rename)',
|
|
238
|
+
},
|
|
239
|
+
remotes: {
|
|
240
|
+
type: 'boolean',
|
|
241
|
+
description: 'Include remote branches in list',
|
|
242
|
+
},
|
|
243
|
+
force: {
|
|
244
|
+
type: 'boolean',
|
|
245
|
+
description: 'Force delete even if not merged',
|
|
246
|
+
},
|
|
247
|
+
startPoint: {
|
|
248
|
+
type: 'string',
|
|
249
|
+
description: 'Start point for new branch',
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
required: ['action'],
|
|
253
|
+
},
|
|
254
|
+
execute: async (input) => {
|
|
255
|
+
let targetPath = input.path ?? '.';
|
|
256
|
+
// Resolve relative paths
|
|
257
|
+
if (options?.baseDir && !targetPath.startsWith('/')) {
|
|
258
|
+
const nodePath = await import('node:path');
|
|
259
|
+
targetPath = nodePath.join(options.baseDir, targetPath);
|
|
260
|
+
}
|
|
261
|
+
return executeGitBranch({ ...input, path: targetPath });
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Commit Tool
|
|
3
|
+
* Creates commits with safety features to prevent accidental commits of secrets
|
|
4
|
+
*/
|
|
5
|
+
import type { Tool } from '@compilr-dev/agents';
|
|
6
|
+
import type { GitCommitInput } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Git commit tool
|
|
9
|
+
*/
|
|
10
|
+
export declare const gitCommitTool: Tool<GitCommitInput>;
|
|
11
|
+
/**
|
|
12
|
+
* Factory function to create git commit tool with custom options
|
|
13
|
+
*/
|
|
14
|
+
export declare function createGitCommitTool(options?: {
|
|
15
|
+
/** Base directory for relative paths */
|
|
16
|
+
baseDir?: string;
|
|
17
|
+
/** Additional secret patterns to check */
|
|
18
|
+
additionalSecretPatterns?: RegExp[];
|
|
19
|
+
/** Disable secret checking */
|
|
20
|
+
disableSecretCheck?: boolean;
|
|
21
|
+
/** Custom large file threshold in bytes */
|
|
22
|
+
largeFileThreshold?: number;
|
|
23
|
+
}): Tool<GitCommitInput>;
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Commit Tool
|
|
3
|
+
* Creates commits with safety features to prevent accidental commits of secrets
|
|
4
|
+
*/
|
|
5
|
+
import { defineTool, createSuccessResult, createErrorResult } from '@compilr-dev/agents';
|
|
6
|
+
import { runGitCommand, isGitRepository, isDirectory } from './utils.js';
|
|
7
|
+
/**
|
|
8
|
+
* Patterns that indicate potential secrets
|
|
9
|
+
*/
|
|
10
|
+
const SECRET_PATTERNS = [
|
|
11
|
+
// Environment files
|
|
12
|
+
/^\.env$/,
|
|
13
|
+
/^\.env\..+$/,
|
|
14
|
+
/\.env\.local$/,
|
|
15
|
+
// Credential files
|
|
16
|
+
/credentials\.json$/,
|
|
17
|
+
/\.credentials$/,
|
|
18
|
+
/secrets\.json$/,
|
|
19
|
+
/secrets\.ya?ml$/,
|
|
20
|
+
// Key files
|
|
21
|
+
/\.pem$/,
|
|
22
|
+
/\.key$/,
|
|
23
|
+
/id_rsa$/,
|
|
24
|
+
/id_ed25519$/,
|
|
25
|
+
/\.p12$/,
|
|
26
|
+
/\.pfx$/,
|
|
27
|
+
// Cloud provider files
|
|
28
|
+
/\.aws\/credentials$/,
|
|
29
|
+
/gcloud.*\.json$/,
|
|
30
|
+
/service[-_]?account.*\.json$/,
|
|
31
|
+
// Token files
|
|
32
|
+
/\.npmrc$/,
|
|
33
|
+
/\.pypirc$/,
|
|
34
|
+
/\.netrc$/,
|
|
35
|
+
];
|
|
36
|
+
/**
|
|
37
|
+
* Large file threshold (1MB)
|
|
38
|
+
*/
|
|
39
|
+
const LARGE_FILE_THRESHOLD = 1024 * 1024;
|
|
40
|
+
/**
|
|
41
|
+
* Git commit tool
|
|
42
|
+
*/
|
|
43
|
+
export const gitCommitTool = defineTool({
|
|
44
|
+
name: 'git_commit',
|
|
45
|
+
description: 'Create a git commit. Includes safety checks for secrets and large files. ' +
|
|
46
|
+
'Can optionally stage files before committing. ' +
|
|
47
|
+
'Use with caution: amend and noVerify options can be dangerous.',
|
|
48
|
+
inputSchema: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {
|
|
51
|
+
path: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: 'Working directory path (default: current directory)',
|
|
54
|
+
},
|
|
55
|
+
message: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
description: 'Commit message (required)',
|
|
58
|
+
},
|
|
59
|
+
files: {
|
|
60
|
+
type: 'array',
|
|
61
|
+
items: { type: 'string' },
|
|
62
|
+
description: 'Specific files to stage before commit',
|
|
63
|
+
},
|
|
64
|
+
addModified: {
|
|
65
|
+
type: 'boolean',
|
|
66
|
+
description: 'Stage all modified/deleted tracked files (git add -u)',
|
|
67
|
+
},
|
|
68
|
+
addAll: {
|
|
69
|
+
type: 'boolean',
|
|
70
|
+
description: 'Stage all files including untracked (git add -A)',
|
|
71
|
+
},
|
|
72
|
+
allowEmpty: {
|
|
73
|
+
type: 'boolean',
|
|
74
|
+
description: 'Allow creating an empty commit',
|
|
75
|
+
},
|
|
76
|
+
amend: {
|
|
77
|
+
type: 'boolean',
|
|
78
|
+
description: 'Amend the previous commit (dangerous!)',
|
|
79
|
+
},
|
|
80
|
+
noVerify: {
|
|
81
|
+
type: 'boolean',
|
|
82
|
+
description: 'Skip pre-commit hooks (dangerous!)',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
required: ['message'],
|
|
86
|
+
},
|
|
87
|
+
execute: executeGitCommit,
|
|
88
|
+
});
|
|
89
|
+
/**
|
|
90
|
+
* Execute git commit
|
|
91
|
+
*/
|
|
92
|
+
async function executeGitCommit(input) {
|
|
93
|
+
const targetPath = input.path ?? process.cwd();
|
|
94
|
+
// Validate message
|
|
95
|
+
if (!input.message || input.message.trim().length === 0) {
|
|
96
|
+
return createErrorResult('Commit message is required');
|
|
97
|
+
}
|
|
98
|
+
// Check if path exists
|
|
99
|
+
if (!(await isDirectory(targetPath))) {
|
|
100
|
+
return createErrorResult(`Directory not found: ${targetPath}`);
|
|
101
|
+
}
|
|
102
|
+
// Check if it's a git repository
|
|
103
|
+
if (!(await isGitRepository(targetPath))) {
|
|
104
|
+
return createErrorResult(`Not a git repository: ${targetPath}`);
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
// Stage files if requested
|
|
108
|
+
if (input.files && input.files.length > 0) {
|
|
109
|
+
const addResult = await runGitCommand(['add', '--', ...input.files], { cwd: targetPath });
|
|
110
|
+
if (!addResult.success) {
|
|
111
|
+
return createErrorResult(`Failed to stage files: ${addResult.stderr}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else if (input.addAll) {
|
|
115
|
+
const addResult = await runGitCommand(['add', '-A'], { cwd: targetPath });
|
|
116
|
+
if (!addResult.success) {
|
|
117
|
+
return createErrorResult(`Failed to stage files: ${addResult.stderr}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else if (input.addModified) {
|
|
121
|
+
const addResult = await runGitCommand(['add', '-u'], { cwd: targetPath });
|
|
122
|
+
if (!addResult.success) {
|
|
123
|
+
return createErrorResult(`Failed to stage files: ${addResult.stderr}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Check what's staged
|
|
127
|
+
const stagedResult = await runGitCommand(['diff', '--cached', '--name-only'], {
|
|
128
|
+
cwd: targetPath,
|
|
129
|
+
});
|
|
130
|
+
const stagedFiles = stagedResult.stdout.split('\n').filter(Boolean);
|
|
131
|
+
// Safety checks
|
|
132
|
+
const warnings = await checkStagedFiles(targetPath, stagedFiles);
|
|
133
|
+
if (warnings.length > 0) {
|
|
134
|
+
const secretWarnings = warnings.filter((w) => w.type === 'secrets');
|
|
135
|
+
if (secretWarnings.length > 0) {
|
|
136
|
+
return createErrorResult(`Refusing to commit potential secrets:\n${secretWarnings.map((w) => ` - ${w.file}: ${w.message}`).join('\n')}\n\nUnstage these files or add them to .gitignore.`);
|
|
137
|
+
}
|
|
138
|
+
// Log other warnings but don't block
|
|
139
|
+
const otherWarnings = warnings.filter((w) => w.type !== 'secrets');
|
|
140
|
+
if (otherWarnings.length > 0) {
|
|
141
|
+
// These are warnings, not errors - we'll include them in success result
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Check if there's anything to commit
|
|
145
|
+
if (stagedFiles.length === 0 && !input.allowEmpty) {
|
|
146
|
+
return createErrorResult('Nothing to commit. Stage files first or use addAll/addModified options.');
|
|
147
|
+
}
|
|
148
|
+
// Build commit command
|
|
149
|
+
const commitArgs = ['commit', '-m', input.message];
|
|
150
|
+
if (input.allowEmpty) {
|
|
151
|
+
commitArgs.push('--allow-empty');
|
|
152
|
+
}
|
|
153
|
+
if (input.amend) {
|
|
154
|
+
commitArgs.push('--amend');
|
|
155
|
+
}
|
|
156
|
+
if (input.noVerify) {
|
|
157
|
+
commitArgs.push('--no-verify');
|
|
158
|
+
}
|
|
159
|
+
// Execute commit
|
|
160
|
+
const commitResult = await runGitCommand(commitArgs, { cwd: targetPath });
|
|
161
|
+
if (!commitResult.success) {
|
|
162
|
+
return createErrorResult(`Commit failed: ${commitResult.stderr}`);
|
|
163
|
+
}
|
|
164
|
+
// Get commit info
|
|
165
|
+
const hashResult = await runGitCommand(['rev-parse', 'HEAD'], { cwd: targetPath });
|
|
166
|
+
const shortHashResult = await runGitCommand(['rev-parse', '--short', 'HEAD'], {
|
|
167
|
+
cwd: targetPath,
|
|
168
|
+
});
|
|
169
|
+
const branchResult = await runGitCommand(['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
170
|
+
cwd: targetPath,
|
|
171
|
+
});
|
|
172
|
+
// Parse stats from commit output
|
|
173
|
+
const statsMatch = commitResult.stdout.match(/(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/);
|
|
174
|
+
const filesCommitted = statsMatch ? parseInt(statsMatch[1], 10) : stagedFiles.length;
|
|
175
|
+
const insertions = statsMatch && statsMatch[2] ? parseInt(statsMatch[2], 10) : 0;
|
|
176
|
+
const deletions = statsMatch && statsMatch[3] ? parseInt(statsMatch[3], 10) : 0;
|
|
177
|
+
const result = {
|
|
178
|
+
hash: hashResult.stdout,
|
|
179
|
+
shortHash: shortHashResult.stdout,
|
|
180
|
+
branch: branchResult.stdout,
|
|
181
|
+
filesCommitted,
|
|
182
|
+
insertions,
|
|
183
|
+
deletions,
|
|
184
|
+
message: input.message,
|
|
185
|
+
};
|
|
186
|
+
return createSuccessResult(result);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
return createErrorResult(error instanceof Error ? error.message : String(error));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Check staged files for potential issues
|
|
194
|
+
*/
|
|
195
|
+
async function checkStagedFiles(repoPath, files) {
|
|
196
|
+
const warnings = [];
|
|
197
|
+
for (const file of files) {
|
|
198
|
+
// Check for secret patterns
|
|
199
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
200
|
+
if (pattern.test(file)) {
|
|
201
|
+
warnings.push({
|
|
202
|
+
type: 'secrets',
|
|
203
|
+
file,
|
|
204
|
+
message: `File matches secret pattern: ${pattern.toString()}`,
|
|
205
|
+
});
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Check file size (only for files, not deletions)
|
|
210
|
+
const sizeResult = await runGitCommand(['cat-file', '-s', `:${file}`], { cwd: repoPath });
|
|
211
|
+
if (sizeResult.success) {
|
|
212
|
+
const size = parseInt(sizeResult.stdout, 10);
|
|
213
|
+
if (size > LARGE_FILE_THRESHOLD) {
|
|
214
|
+
warnings.push({
|
|
215
|
+
type: 'large_file',
|
|
216
|
+
file,
|
|
217
|
+
message: `File is ${String(Math.round(size / 1024))}KB (threshold: ${String(Math.round(LARGE_FILE_THRESHOLD / 1024))}KB)`,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return warnings;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Factory function to create git commit tool with custom options
|
|
226
|
+
*/
|
|
227
|
+
export function createGitCommitTool(options) {
|
|
228
|
+
return defineTool({
|
|
229
|
+
name: 'git_commit',
|
|
230
|
+
description: 'Create a git commit. Includes safety checks for secrets and large files.',
|
|
231
|
+
inputSchema: {
|
|
232
|
+
type: 'object',
|
|
233
|
+
properties: {
|
|
234
|
+
path: {
|
|
235
|
+
type: 'string',
|
|
236
|
+
description: 'Working directory path (default: current directory)',
|
|
237
|
+
},
|
|
238
|
+
message: {
|
|
239
|
+
type: 'string',
|
|
240
|
+
description: 'Commit message (required)',
|
|
241
|
+
},
|
|
242
|
+
files: {
|
|
243
|
+
type: 'array',
|
|
244
|
+
items: { type: 'string' },
|
|
245
|
+
description: 'Specific files to stage before commit',
|
|
246
|
+
},
|
|
247
|
+
addModified: {
|
|
248
|
+
type: 'boolean',
|
|
249
|
+
description: 'Stage all modified/deleted tracked files',
|
|
250
|
+
},
|
|
251
|
+
addAll: {
|
|
252
|
+
type: 'boolean',
|
|
253
|
+
description: 'Stage all files including untracked',
|
|
254
|
+
},
|
|
255
|
+
allowEmpty: {
|
|
256
|
+
type: 'boolean',
|
|
257
|
+
description: 'Allow creating an empty commit',
|
|
258
|
+
},
|
|
259
|
+
amend: {
|
|
260
|
+
type: 'boolean',
|
|
261
|
+
description: 'Amend the previous commit',
|
|
262
|
+
},
|
|
263
|
+
noVerify: {
|
|
264
|
+
type: 'boolean',
|
|
265
|
+
description: 'Skip pre-commit hooks',
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
required: ['message'],
|
|
269
|
+
},
|
|
270
|
+
execute: async (input) => {
|
|
271
|
+
let targetPath = input.path ?? '.';
|
|
272
|
+
// Resolve relative paths
|
|
273
|
+
if (options?.baseDir && !targetPath.startsWith('/')) {
|
|
274
|
+
const nodePath = await import('node:path');
|
|
275
|
+
targetPath = nodePath.join(options.baseDir, targetPath);
|
|
276
|
+
}
|
|
277
|
+
return executeGitCommit({ ...input, path: targetPath });
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Diff Tool
|
|
3
|
+
* Shows changes between commits, working tree, etc.
|
|
4
|
+
*/
|
|
5
|
+
import type { Tool } from '@compilr-dev/agents';
|
|
6
|
+
import type { GitDiffInput } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Git diff tool
|
|
9
|
+
*/
|
|
10
|
+
export declare const gitDiffTool: Tool<GitDiffInput>;
|
|
11
|
+
/**
|
|
12
|
+
* Factory function to create git diff tool with custom options
|
|
13
|
+
*/
|
|
14
|
+
export declare function createGitDiffTool(options?: {
|
|
15
|
+
/** Base directory for relative paths */
|
|
16
|
+
baseDir?: string;
|
|
17
|
+
/** Default context lines */
|
|
18
|
+
defaultContext?: number;
|
|
19
|
+
}): Tool<GitDiffInput>;
|